From 2b500f194aad05f07dd92d0ab054fe71536884e4 Mon Sep 17 00:00:00 2001 From: trapexit Date: Mon, 21 Jul 2025 18:18:59 -0500 Subject: [PATCH] Rework of runtime interface (#1498) Also have special error handling for when branches are invalid and ENOENT would be returned for getattr and readdir so users understand what is going on and the runtime interface can still be used to fix the problem. --- Makefile | 7 + mkdocs/docs/faq/usage_and_functionality.md | 2 +- mkdocs/docs/faq/why_isnt_it_working.md | 8 +- mkdocs/docs/runtime_interface.md | 180 ++ mkdocs/docs/runtime_interfaces.md | 114 - mkdocs/docs/setup/upgrade.md | 2 +- mkdocs/mkdocs.yml | 2 +- src/config.cpp | 86 +- src/config.hpp | 10 + src/fs_attr_linux.icpp | 32 +- src/fs_clonepath.cpp | 2 +- src/fs_copyfile.cpp | 2 +- src/fs_ficlone_linux.icpp | 2 +- src/fs_ioctl.hpp | 20 +- src/fs_llistxattr.hpp | 12 +- src/fuse_getattr.cpp | 44 +- src/fuse_getattr.hpp | 2 + src/fuse_getxattr.cpp | 92 +- src/fuse_getxattr.hpp | 6 +- src/fuse_ioctl.cpp | 381 +--- src/fuse_listxattr.cpp | 192 +- src/fuse_listxattr.hpp | 1 + src/fuse_open.cpp | 2 +- src/fuse_readdir.cpp | 45 +- src/fuse_removexattr.cpp | 2 +- src/fuse_setxattr.cpp | 328 +-- src/fuse_setxattr.hpp | 2 + src/fuse_statx_supported.icpp | 42 +- src/gidcache.cpp | 26 +- src/gidcache.hpp | 12 - src/mergerfs.cpp | 14 +- src/mergerfs_api.cpp | 47 +- src/mergerfs_collect_info.cpp | 86 +- src/mergerfs_ioctl.hpp | 23 +- src/nonstd/string.hpp | 2311 ++++++++++++++++++++ src/str.cpp | 49 + src/str.hpp | 14 + 37 files changed, 3378 insertions(+), 824 deletions(-) create mode 100644 mkdocs/docs/runtime_interface.md delete mode 100644 mkdocs/docs/runtime_interfaces.md create mode 100644 src/nonstd/string.hpp diff --git a/Makefile b/Makefile index 46b9fdfd..a2c7a260 100644 --- a/Makefile +++ b/Makefile @@ -293,5 +293,12 @@ release-static: --cleanup \ --branch=$(shell git branch --show-current) +tags: + rm -fv TAGS + find . -name "*.c" -print | etags --append - + find . -name "*.h" -print | etags --append - + find . -name "*.cpp" -print | etags --append - + find . -name "*.hpp" -print | etags --append - + -include $(DEPS) diff --git a/mkdocs/docs/faq/usage_and_functionality.md b/mkdocs/docs/faq/usage_and_functionality.md index 9f1f407a..88767487 100644 --- a/mkdocs/docs/faq/usage_and_functionality.md +++ b/mkdocs/docs/faq/usage_and_functionality.md @@ -26,7 +26,7 @@ Yes. See previous question's answer. This is true for planned removal by unmounting mergerfs and changing the config, changes made to mergerfs at -[runtime](../runtime_interfaces.md), umounting of the branch's +[runtime](../runtime_interface.md), umounting of the branch's filesystem on the fly (whether on purpose or due to error), etc. diff --git a/mkdocs/docs/faq/why_isnt_it_working.md b/mkdocs/docs/faq/why_isnt_it_working.md index 2b1df59c..93430e6d 100644 --- a/mkdocs/docs/faq/why_isnt_it_working.md +++ b/mkdocs/docs/faq/why_isnt_it_working.md @@ -7,9 +7,9 @@ mount time. You can not simply modify the [source of the configuration](../quickstart.md#usage) and have those settings applied any more than you would for other filesystems. It is the user's responsibility to [restart](../setup/upgrade.md) mergerfs to pick up -the changes or use the [runtime interface](../runtime_interfaces.md). +the changes or use the [runtime interface](../runtime_interface.md). -NOTE: the [runtime interface](../runtime_interfaces.md) is **just** +NOTE: the [runtime interface](../runtime_interface.md) is **just** for runtime changes. It does **NOT** save those changed values anywhere. @@ -64,7 +64,7 @@ theoretical space available. Not the practical usable space. It probably is. The policies rather straight forward and well tested. First, confirm the policy is configured as expected by using the -[runtime interface](../runtime_interfaces.md). +[runtime interface](../runtime_interface.md). ```shell $ sudo getfattr -n user.mergerfs.category.create /mnt/mergerfs/.mergerfs @@ -246,4 +246,4 @@ possible options rather than being given to the kernel where those options in the `mount` command and /proc/mounts come from. If you want to see the options of a running instance of mergerfs you -can use the [runtime interface](../runtime_interfaces.md). +can use the [runtime interface](../runtime_interface.md). diff --git a/mkdocs/docs/runtime_interface.md b/mkdocs/docs/runtime_interface.md new file mode 100644 index 00000000..d0bfc97f --- /dev/null +++ b/mkdocs/docs/runtime_interface.md @@ -0,0 +1,180 @@ +# Runtime Interface + +`mergerfs` has runtime interfaces allowing users to query certain +filesystem information, get and set config, and trigger certain +activities while it is running. + +The interface is provided via the POSIX extended attributes filesystem +API which is a namespaced `key=value` pair store associated with a +file. Since `mergerfs` primarily uses `key=value` pairs for config it +fits well and is a known and reasonably well supported API. + +There are two targets for `xattr` calls. One is a pseudo file used for +getting and setting config and issuing certain commands. The other are +files found on the filesystem for querying certain `mergerfs` specific +information about them. + + +## .mergerfs pseudo file + +``` +/.mergerfs +``` + +`mergerfs` provides this pseudo file for the runtime modification of +certain options and issuing commands. The file will not show up in +`readdir` but can be `stat`'ed and viewed/manipulated via +[listxattr](http://linux.die.net/man/2/listxattr), +[getxattr](https://linux.die.net/man/2/getxattr), and +[setxattr](https://linux.die.net/man/2/setxattr) calls. + +Any changes made at runtime are **NOT** persisted. If you wish for +values to persist they must be included as options wherever you +configure the mounting of mergerfs (/etc/fstab, systemd, etc.). + + +### Command Line Tooling + +Extended attributes is prevelant enough that there are common tools +available for interacting with them. + +In Debian / Ubuntu distributions you can get the tools +[getfattr](https://linux.die.net/man/1/getfattr) and +[setfattr](https://linux.die.net/man/1/setfattr) from +[attr](https://linux.die.net/man/5/attr) package. + +``` +$ sudo apt-get install attr +``` + + +### Config + +#### Keys + +Use `getfattr -d /mountpoint/.mergerfs` to see all supported +configuration keys. It is effectively the same as the +[options](config/options.md) prefixed with `user.mergerfs.`. Some are +informational or only can be set at startup and therefore read-only. + +Example: option `cache.files` would be `user.mergerfs.cache.files`. + + +#### Values + +Same as the [command line options](config/options.md). + + +#### Getting + +`getfattr -n user.mergerfs.branches /mountpoint/.mergerfs` + +`ENOATTR` will be returned if the key doesn't exist as normal with +[getxattr](https://linux.die.net/man/2/getxattr). + + +#### Setting + +`setfattr -n user.mergerfs.branches -v VALUE /mountpoint/.mergerfs` + +[setxattr](https://linux.die.net/man/2/setxattr) will return `EROFS` +(Read-only filesystem) on read-only keys. `ENOATTR` will be returned +if the key does not exist. If the value attempting to be set is not +valid `EINVAL` will be returned. + + +#### user.mergerfs.branches + +`branches` has the ability to understand some simple instructions to +make manipulation of the list easier. The `[list]` is simply what is +described in the [branches](config/branches.md) docs. + +| Value | Description | +| -------- | -------------------------- | +| [list] | set | +| +<[list] | prepend to existing list | +| +>[list] | append to existing list | +| -[list] | remove all values provided | +| -< | remove first in list | +| -> | remove last in list | + +**NOTE:** if the value of `branches` is set to something invalid / +non-existant `mergerfs` will return a bogus entry when the mount point +directory is `stat`'ed and create a fake file entry when listing the +directory telling the user "error: no valid mergerfs branch found, +check config". This is done to ensure the user understands the +situation and continue to be able to access the xattr interface. + + +#### Example + +``` +[trapexit:/mnt/mergerfs] $ getfattr -d .mergerfs +user.mergerfs.branches="/mnt/a=RW:/mnt/b=RW" +user.mergerfs.minfreespace="4294967295" +user.mergerfs.moveonenospc="false" +... + +[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.category.create .mergerfs +user.mergerfs.category.search="mfs" + +[trapexit:/mnt/mergerfs] $ setfattr -n user.mergerfs.category.create -v pfrd .mergerfs +[trapexit:/mnt/mergerfs] $ getfattr -n user.mergerfs.category.create .mergerfs +user.mergerfs.category.search="prfd" + +[trapexit:/mnt/mergerfs] $ setfattr -n user.mergerfs.branches -v "'+/.mergerfs -``` - -There is a pseudo file available at the mount point which allows for -the runtime modification of certain **mergerfs** options. The file -will not show up in **readdir** but can be **stat**'ed and manipulated -via [{list,get,set}xattrs](http://linux.die.net/man/2/listxattr) -calls. - -Any changes made at runtime are **not** persisted. If you wish for -values to persist they must be included as options wherever you -configure the mounting of mergerfs (/etc/fstab). - - -#### Keys - -Use `getfattr -d /mountpoint/.mergerfs` or `xattr -l -/mountpoint/.mergerfs` to see all supported keys. Some are -informational and therefore read-only. `setxattr` will return EINVAL -(invalid argument) on read-only keys. - - -#### Values - -Same as the command line. - - -##### user.mergerfs.branches - -Used to query or modify the list of branches. When modifying there are -several shortcuts to easy manipulation of the list. - -| Value | Description | -| -------- | -------------------------- | -| [list] | set | -| +<[list] | prepend | -| +>[list] | append | -| -[list] | remove all values provided | -| -< | remove first in list | -| -> | remove last in list | - -`xattr -w user.mergerfs.branches +`. Or you can let mergerfs do it by setting the option `lazy-umount-mountpoint=true`. If the intent is to change settings at runtime then the [runtime -interface](../runtime_interfaces.md) should be used. +interface](../runtime_interface.md) should be used. diff --git a/mkdocs/mkdocs.yml b/mkdocs/mkdocs.yml index 74f03487..4cc6acae 100644 --- a/mkdocs/mkdocs.yml +++ b/mkdocs/mkdocs.yml @@ -95,7 +95,7 @@ nav: - config/export-support.md - config/kernel-permissions-check.md - error_handling_and_logging.md -- runtime_interfaces.md +- runtime_interface.md - remote_filesystems.md - tips_notes.md - known_issues_bugs.md diff --git a/src/config.cpp b/src/config.cpp index effd687a..d5408bb4 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -23,6 +23,7 @@ #include "str.hpp" #include "to_string.hpp" #include "version.hpp" +#include "nonstd/string.hpp" #include #include @@ -39,7 +40,6 @@ #define IFERT(S) if(S == s_) return true -const std::string CONTROLFILE = "/.mergerfs"; constexpr static const char CACHE_FILES_PROCESS_NAMES_DEFAULT[] = "rtorrent|" "qbittorrent-nox"; @@ -47,7 +47,6 @@ constexpr static const char CACHE_FILES_PROCESS_NAMES_DEFAULT[] = Config Config::_singleton; - namespace l { static @@ -277,6 +276,45 @@ Config::keys_xattr(std::string &s_) const } } +ssize_t +Config::keys_listxattr_size() const +{ + ssize_t rv; + + rv = 0; + for(const auto &[key,val] : _map) + { + rv += sizeof("user.mergerfs."); + rv += key.size(); + } + + return rv; +} + +ssize_t +Config::keys_listxattr(char *list_, + size_t size_) const +{ + char *list = list_; + ssize_t size = size_; + + if(size_ == 0) + return keys_listxattr_size(); + + for(const auto &[key,val] : _map) + { + auto rv = fmt::format_to_n(list,size, + "user.mergerfs.{}\0", + key); + if(rv.out >= (list + size)) + return -ERANGE; + list += rv.size; + size -= rv.size; + } + + return (list - list_); +} + int Config::get(const std::string &key_, std::string *val_) const @@ -390,6 +428,50 @@ Config::finish_initializing() _initialized = true; } +bool +Config::is_rootdir(const char *fusepath_) +{ + return str::eq(fusepath_,"/"); +} + +bool +Config::is_ctrl_file(const char *fusepath_) +{ + return str::eq(fusepath_,"/.mergerfs"); +} + +bool +Config::is_mergerfs_xattr(const char *attrname_) +{ + return str::startswith(attrname_,"user.mergerfs."); +} + +bool +Config::is_cmd_xattr(const std::string_view &attrname_) +{ + return nonstd::string::starts_with(attrname_,"user.mergerfs.cmd."); +} + +std::string +Config::prune_ctrl_xattr(const std::string &s_) +{ + const size_t offset = (sizeof("user.mergerfs.") - 1); + + if(offset < s_.size()) + return s_.substr(offset); + return {}; +} + +std::string_view +Config::prune_cmd_xattr(const std::string_view &s_) +{ + constexpr size_t offset = (sizeof("user.mergerfs.cmd.") - 1); + + if(offset < s_.size()) + return s_.substr(offset); + return {}; +} + std::ostream& operator<<(std::ostream &os_, const Config &c_) diff --git a/src/config.hpp b/src/config.hpp index e1cc7188..879bf2e4 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -184,6 +184,8 @@ public: bool has_key(const std::string &key) const; void keys(std::string &s) const; void keys_xattr(std::string &s) const; + ssize_t keys_listxattr(char *list, size_t size) const; + ssize_t keys_listxattr_size() const; public: int get(const std::string &key, std::string *val) const; @@ -195,6 +197,14 @@ public: int from_stream(std::istream &istrm, ErrVec *errs); int from_file(const std::string &filepath, ErrVec *errs); +public: + static bool is_rootdir(const char *fusepath); + static bool is_ctrl_file(const char *fusepath); + static bool is_mergerfs_xattr(const char *attrname); + static bool is_cmd_xattr(const std::string_view &attrname); + static std::string prune_ctrl_xattr(const std::string &s); + static std::string_view prune_cmd_xattr(const std::string_view &s); + private: Str2TFStrMap _map; diff --git a/src/fs_attr_linux.icpp b/src/fs_attr_linux.icpp index 43bdecba..a5a3c3c6 100644 --- a/src/fs_attr_linux.icpp +++ b/src/fs_attr_linux.icpp @@ -36,8 +36,8 @@ _get_fs_ioc_flags(const int fd, int rv; rv = fs::ioctl(fd,FS_IOC_GETFLAGS,(void*)&flags); - if((rv == -1) && (errno == EINVAL)) - errno = ENOTSUP; + if(rv == -EINVAL) + rv = ENOTSUP; return rv; } @@ -53,15 +53,13 @@ _get_fs_ioc_flags(const string &file, fd = fs::open(file,openflags); if(fd == -1) - return -1; + return -errno; rv = ::_get_fs_ioc_flags(fd,flags); - if(rv == -1) + if(rv < 0) { - int error = errno; fs::close(fd); - errno = error; - return -1; + return rv; } return fs::close(fd); @@ -75,8 +73,8 @@ _set_fs_ioc_flags(const int fd, int rv; rv = fs::ioctl(fd,FS_IOC_SETFLAGS,(void*)&flags); - if((rv == -1) && (errno == EINVAL)) - errno = ENOTSUP; + if(rv == -EINVAL) + rv = ENOTSUP; return rv; } @@ -92,15 +90,13 @@ _set_fs_ioc_flags(const string &file, fd = fs::open(file,openflags); if(fd == -1) - return -1; + return -errno; rv = ::_set_fs_ioc_flags(fd,flags); - if(rv == -1) + if(rv < 0) { - int error = errno; fs::close(fd); - errno = error; - return -1; + return rv; } return fs::close(fd); @@ -115,8 +111,8 @@ fs::attr::copy(const int fdin, int flags; rv = ::_get_fs_ioc_flags(fdin,flags); - if(rv == -1) - return -1; + if(rv < 0) + return rv; if(flags_ & FS_ATTR_CLEAR_IMMUTABLE) flags = (flags & ~FS_IMMUTABLE_FL); @@ -132,8 +128,8 @@ fs::attr::copy(const string &from, int flags; rv = ::_get_fs_ioc_flags(from,flags); - if(rv == -1) - return -1; + if(rv < 0) + return rv; return ::_set_fs_ioc_flags(to,flags); } diff --git a/src/fs_clonepath.cpp b/src/fs_clonepath.cpp index 4fec3ae7..bee9e27c 100644 --- a/src/fs_clonepath.cpp +++ b/src/fs_clonepath.cpp @@ -100,7 +100,7 @@ namespace fs // it may not support it... it's fine... rv = fs::attr::copy(frompath,topath); - if(return_metadata_errors_ && (rv == -1) && !l::ignorable_error(errno)) + if(return_metadata_errors_ && (rv < 0) && !l::ignorable_error(-rv)) return -1; // it may not support it... it's fine... diff --git a/src/fs_copyfile.cpp b/src/fs_copyfile.cpp index 4d8caf29..4747232f 100644 --- a/src/fs_copyfile.cpp +++ b/src/fs_copyfile.cpp @@ -54,7 +54,7 @@ fs::copyfile(const int src_fd_, return -1; rv = fs::attr::copy(src_fd_,dst_fd_,FS_ATTR_CLEAR_IMMUTABLE); - if((rv == -1) && !::_ignorable_error(errno)) + if((rv < 0) && !::_ignorable_error(-rv)) return -1; rv = fs::fchown_check_on_error(dst_fd_,src_st_); diff --git a/src/fs_ficlone_linux.icpp b/src/fs_ficlone_linux.icpp index f0ce01bd..4de556ca 100644 --- a/src/fs_ficlone_linux.icpp +++ b/src/fs_ficlone_linux.icpp @@ -31,7 +31,7 @@ namespace fs #ifdef FICLONE return fs::ioctl(dst_fd_,FICLONE,src_fd_); #else - return (errno=EOPNOTSUPP,-1); + return -EOPNOTSUPP; #endif } } diff --git a/src/fs_ioctl.hpp b/src/fs_ioctl.hpp index 86ee9bdf..4d7d7f66 100644 --- a/src/fs_ioctl.hpp +++ b/src/fs_ioctl.hpp @@ -18,6 +18,8 @@ #pragma once +#include "errno.hpp" + #include @@ -29,7 +31,11 @@ namespace fs ioctl(const int fd_, const unsigned long request_) { - return ::ioctl(fd_,request_); + int rv; + + rv = ::ioctl(fd_,request_); + + return ((rv == -1) ? -errno : rv); } static @@ -39,7 +45,11 @@ namespace fs const unsigned long request_, void *data_) { - return ::ioctl(fd_,request_,data_); + int rv; + + rv = ::ioctl(fd_,request_,data_); + + return ((rv == -1) ? -errno : rv); } static @@ -49,6 +59,10 @@ namespace fs const unsigned long request_, const int int_) { - return ::ioctl(fd_,request_,int_); + int rv; + + rv = ::ioctl(fd_,request_,int_); + + return ((rv == -1) ? -errno : rv); } } diff --git a/src/fs_llistxattr.hpp b/src/fs_llistxattr.hpp index 9818a665..18607f2c 100644 --- a/src/fs_llistxattr.hpp +++ b/src/fs_llistxattr.hpp @@ -30,21 +30,25 @@ namespace fs { static inline - int + ssize_t llistxattr(const char *path_, char *list_, const size_t size_) { #ifdef USE_XATTR - return ::llistxattr(path_,list_,size_); + ssize_t rv; + + rv = ::llistxattr(path_,list_,size_); + + return ((rv == -1) ? -errno : rv); #else - return (errno=ENOTSUP,-1); + return -ENOTSUP; #endif } static inline - int + ssize_t llistxattr(const std::string &path_, char *list_, const size_t size_) diff --git a/src/fuse_getattr.cpp b/src/fuse_getattr.cpp index c0dae147..347a5d60 100644 --- a/src/fuse_getattr.cpp +++ b/src/fuse_getattr.cpp @@ -14,6 +14,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "fuse_getattr.hpp" + #include "config.hpp" #include "errno.hpp" #include "fs_fstat.hpp" @@ -23,6 +25,7 @@ #include "fs_stat.hpp" #include "fuse_fgetattr.hpp" #include "state.hpp" +#include "str.hpp" #include "symlinkify.hpp" #include "ugid.hpp" @@ -71,6 +74,27 @@ namespace l return; } + static + int + getattr_fake_root(struct stat *st_) + { + st_->st_dev = 0; + st_->st_ino = 0; + st_->st_mode = (S_IFDIR|S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); + st_->st_nlink = 2; + st_->st_uid = 0; + st_->st_gid = 0; + st_->st_rdev = 0; + st_->st_size = 0; + st_->st_blksize = 512; + st_->st_blocks = 0; + st_->st_atime = 0; + st_->st_mtime = 0; + st_->st_ctime = 0; + + return 0; + } + static int getattr_controlfile(struct stat *st_) @@ -166,6 +190,8 @@ namespace l cfg->symlinkify, cfg->symlinkify_timeout, cfg->follow_symlinks); + if((rv < 0) && Config::is_rootdir(fusepath_)) + return l::getattr_fake_root(st_); timeout_->entry = ((rv >= 0) ? cfg->cache_entry : @@ -176,16 +202,14 @@ namespace l } } -namespace FUSE + +int +FUSE::getattr(const char *fusepath_, + struct stat *st_, + fuse_timeouts_t *timeout_) { - int - getattr(const char *fusepath_, - struct stat *st_, - fuse_timeouts_t *timeout_) - { - if(fusepath_ == CONTROLFILE) - return l::getattr_controlfile(st_); + if(Config::is_ctrl_file(fusepath_)) + return l::getattr_controlfile(st_); - return l::getattr(fusepath_,st_,timeout_); - } + return l::getattr(fusepath_,st_,timeout_); } diff --git a/src/fuse_getattr.hpp b/src/fuse_getattr.hpp index 03e4b6c1..055af104 100644 --- a/src/fuse_getattr.hpp +++ b/src/fuse_getattr.hpp @@ -16,6 +16,8 @@ #pragma once +#include "fuse.h" + #include #include #include diff --git a/src/fuse_getxattr.cpp b/src/fuse_getxattr.cpp index d00e429f..32156538 100644 --- a/src/fuse_getxattr.cpp +++ b/src/fuse_getxattr.cpp @@ -14,6 +14,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "fuse_getxattr.hpp" + #include "config.hpp" #include "errno.hpp" #include "fs_findallfiles.hpp" @@ -61,36 +63,32 @@ namespace l static int - getxattr_controlfile(Config::Read &cfg_, - const char *attrname_, - char *buf_, - const size_t count_) + getxattr_ctrl_file(Config::Read &cfg_, + const char *attrname_, + char *buf_, + const size_t count_) { int rv; - size_t len; std::string key; std::string val; - StrVec attr; - if(!str::startswith(attrname_,"user.mergerfs.")) + if(!Config::is_mergerfs_xattr(attrname_)) return -ENOATTR; - key = &attrname_[14]; + key = Config::prune_ctrl_xattr(attrname_); rv = cfg_->get(key,&val); if(rv < 0) return rv; - len = val.size(); - if(count_ == 0) - return len; + return val.size(); - if(count_ < len) + if(count_ < val.size()) return -ERANGE; - memcpy(buf_,val.c_str(),len); + memcpy(buf_,val.c_str(),val.size()); - return (int)len; + return (int)val.size(); } static @@ -142,17 +140,17 @@ namespace l char *buf_, const size_t count_) { - StrVec attr; + std::string key; - str::split(attrname_,'.',&attr); + key = Config::prune_ctrl_xattr(attrname_); - if(attr[2] == "basepath") + if(key == "basepath") return l::getxattr_from_string(buf_,count_,basepath_); - else if(attr[2] == "relpath") + if(key == "relpath") return l::getxattr_from_string(buf_,count_,fusepath_); - else if(attr[2] == "fullpath") + if(key == "fullpath") return l::getxattr_from_string(buf_,count_,fullpath_); - else if(attr[2] == "allpaths") + if(key == "allpaths") return l::getxattr_user_mergerfs_allpaths(branches_,fusepath_,buf_,count_); return -ENOATTR; @@ -177,7 +175,7 @@ namespace l fullpath = fs::path::make(branches[0]->path,fusepath_); - if(str::startswith(attrname_,"user.mergerfs.")) + if(Config::is_mergerfs_xattr(attrname_)) return l::getxattr_user_mergerfs(branches[0]->path, fusepath_, fullpath, @@ -190,37 +188,35 @@ namespace l } } -namespace FUSE + +int +FUSE::getxattr(const char *fusepath_, + const char *attrname_, + char *attrvalue_, + size_t attrvalue_size_) { - int - getxattr(const char *fusepath_, - const char *attrname_, - char *buf_, - size_t count_) - { - Config::Read cfg; + Config::Read cfg; - if(fusepath_ == CONTROLFILE) - return l::getxattr_controlfile(cfg, - attrname_, - buf_, - count_); + if(Config::is_ctrl_file(fusepath_)) + return l::getxattr_ctrl_file(cfg, + attrname_, + attrvalue_, + attrvalue_size_); - if((cfg->security_capability == false) && - l::is_attrname_security_capability(attrname_)) - return -ENOATTR; + if((cfg->security_capability == false) && + l::is_attrname_security_capability(attrname_)) + return -ENOATTR; - if(cfg->xattr.to_int()) - return -cfg->xattr.to_int(); + if(cfg->xattr.to_int()) + return -cfg->xattr.to_int(); - const fuse_context *fc = fuse_get_context(); - const ugid::Set ugid(fc->uid,fc->gid); + const fuse_context *fc = fuse_get_context(); + const ugid::Set ugid(fc->uid,fc->gid); - return l::getxattr(cfg->func.getxattr.policy, - cfg->branches, - fusepath_, - attrname_, - buf_, - count_); - } + return l::getxattr(cfg->func.getxattr.policy, + cfg->branches, + fusepath_, + attrname_, + attrvalue_, + attrvalue_size_); } diff --git a/src/fuse_getxattr.hpp b/src/fuse_getxattr.hpp index 60869c87..a9f550e1 100644 --- a/src/fuse_getxattr.hpp +++ b/src/fuse_getxattr.hpp @@ -16,12 +16,14 @@ #pragma once +#include + namespace FUSE { int getxattr(const char *fusepath, const char *attrname, - char *buf, - size_t count); + char *attrvalue, + size_t attrvalue_size); } diff --git a/src/fuse_ioctl.cpp b/src/fuse_ioctl.cpp index 92c2cd40..b65c305c 100644 --- a/src/fuse_ioctl.cpp +++ b/src/fuse_ioctl.cpp @@ -14,6 +14,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "fuse_ioctl.hpp" + +#include "fuse_getxattr.hpp" +#include "fuse_setxattr.hpp" + #include "config.hpp" #include "dirinfo.hpp" #include "endian.hpp" @@ -73,295 +78,123 @@ https://lwn.net/Articles/575846/ */ -namespace l +static +int +_ioctl(const int fd_, + const uint32_t cmd_, + void *data_, + uint32_t *out_bufsz_) +{ + int rv; + + switch(cmd_) + { + case FS_IOC_GETFLAGS: + case FS_IOC_SETFLAGS: + case FS_IOC_GETVERSION: + case FS_IOC_SETVERSION: + if(endian::is_big() && (sizeof(long) != sizeof(int))) + return -ENOTTY; + if((data_ != NULL) && (*out_bufsz_ > 4)) + *out_bufsz_ = 4; + break; + } + + rv = fs::ioctl(fd_,cmd_,data_); + + return rv; +} + +static +int +_ioctl_file(const fuse_file_info_t *ffi_, + const uint32_t cmd_, + void *data_, + uint32_t *out_bufsz_) { - static - int - ioctl(const int fd_, - const uint32_t cmd_, - void *data_, - uint32_t *out_bufsz_) - { - int rv; - - switch(cmd_) - { - case FS_IOC_GETFLAGS: - case FS_IOC_SETFLAGS: - case FS_IOC_GETVERSION: - case FS_IOC_SETVERSION: - if(endian::is_big() && (sizeof(long) != sizeof(int))) - return -ENOTTY; - if((data_ != NULL) && (*out_bufsz_ > 4)) - *out_bufsz_ = 4; - break; - } - - rv = fs::ioctl(fd_,cmd_,data_); - - return ((rv == -1) ? -errno : rv); - } - - static - int - ioctl_file(const fuse_file_info_t *ffi_, - const uint32_t cmd_, - void *data_, - uint32_t *out_bufsz_) - { - FileInfo *fi = reinterpret_cast(ffi_->fh); - const fuse_context *fc = fuse_get_context(); - const ugid::Set ugid(fc->uid,fc->gid); - - return l::ioctl(fi->fd,cmd_,data_,out_bufsz_); - } + FileInfo *fi = reinterpret_cast(ffi_->fh); + const fuse_context *fc = fuse_get_context(); + const ugid::Set ugid(fc->uid,fc->gid); + + return ::_ioctl(fi->fd,cmd_,data_,out_bufsz_); +} #ifndef O_NOATIME #define O_NOATIME 0 #endif - static - int - ioctl_dir_base(const Policy::Search &searchFunc_, - const Branches &branches_, - const char *fusepath_, - const uint32_t cmd_, - void *data_, - uint32_t *out_bufsz_) - { - int fd; - int rv; - std::string fullpath; - std::vector branches; +static +int +_ioctl_dir_base(const Policy::Search &searchFunc_, + const Branches &branches_, + const char *fusepath_, + const uint32_t cmd_, + void *data_, + uint32_t *out_bufsz_) +{ + int fd; + int rv; + std::string fullpath; + std::vector branches; - rv = searchFunc_(branches_,fusepath_,branches); - if(rv == -1) - return -errno; + rv = searchFunc_(branches_,fusepath_,branches); + if(rv == -1) + return -errno; - fullpath = fs::path::make(branches[0]->path,fusepath_); + fullpath = fs::path::make(branches[0]->path,fusepath_); - fd = fs::open(fullpath,O_RDONLY|O_NOATIME|O_NONBLOCK); - if(fd == -1) - return -errno; + fd = fs::open(fullpath,O_RDONLY|O_NOATIME|O_NONBLOCK); + if(fd == -1) + return -errno; - rv = l::ioctl(fd,cmd_,data_,out_bufsz_); + rv = ::_ioctl(fd,cmd_,data_,out_bufsz_); - fs::close(fd); + fs::close(fd); - return rv; - } + return rv; +} - static - int - ioctl_dir(const fuse_file_info_t *ffi_, - const uint32_t cmd_, - void *data_, - uint32_t *out_bufsz_) - { - Config::Read cfg; - DirInfo *di = reinterpret_cast(ffi_->fh); - const fuse_context *fc = fuse_get_context(); - const ugid::Set ugid(fc->uid,fc->gid); - - return l::ioctl_dir_base(cfg->func.open.policy, - cfg->branches, - di->fusepath.c_str(), - cmd_, - data_, - out_bufsz_); - } - - static - int - strcpy(const std::string &s_, - void *data_) - { - char *data = (char*)data_; - - if(s_.size() >= (sizeof(IOCTL_BUF) - 1)) - return -ERANGE; - - memcpy(data,s_.c_str(),s_.size()); - data[s_.size()] = '\0'; - - return s_.size(); - } - - static - int - file_basepath(const Policy::Search &searchFunc_, - const Branches &branches_, - const char *fusepath_, - void *data_) - { - int rv; - std::vector branches; - - rv = searchFunc_(branches_,fusepath_,branches); - if(rv == -1) - return -errno; - - return l::strcpy(branches[0]->path,data_); - } - - static - int - file_basepath(const fuse_file_info_t *ffi_, - void *data_) - { - Config::Read cfg; - std::string &fusepath = reinterpret_cast(ffi_->fh)->fusepath; - - return l::file_basepath(cfg->func.open.policy, - cfg->branches, - fusepath.c_str(), - data_); - } - - static - int - file_relpath(const fuse_file_info_t *ffi_, - void *data_) - { - std::string &fusepath = reinterpret_cast(ffi_->fh)->fusepath; - - return l::strcpy(fusepath,data_); - } - - static - int - file_fullpath(const Policy::Search &searchFunc_, - const Branches &ibranches_, - const std::string &fusepath_, - void *data_) - { - int rv; - StrVec basepaths; - std::string fullpath; - std::vector obranches; - - rv = searchFunc_(ibranches_,fusepath_,obranches); - if(rv == -1) - return -errno; - - fullpath = fs::path::make(obranches[0]->path,fusepath_); - - return l::strcpy(fullpath,data_); - } - - static - int - file_fullpath(const fuse_file_info_t *ffi_, - void *data_) - { - Config::Read cfg; - std::string &fusepath = reinterpret_cast(ffi_->fh)->fusepath; - - return l::file_fullpath(cfg->func.open.policy, - cfg->branches, - fusepath, - data_); - } - - static - int - file_allpaths(const fuse_file_info_t *ffi_, - void *data_) - { - Config::Read cfg; - std::string concated; - StrVec paths; - StrVec branches; - std::string &fusepath = reinterpret_cast(ffi_->fh)->fusepath; - - cfg->branches->to_paths(branches); - - fs::findallfiles(branches,fusepath.c_str(),&paths); - - concated = str::join(paths,'\0'); - - return l::strcpy(concated,data_); - } - - static - int - file_info(const fuse_file_info_t *ffi_, - void *data_) - { - char *key = (char*)data_; - - if(!strcmp("basepath",key)) - return l::file_basepath(ffi_,data_); - if(!strcmp("relpath",key)) - return l::file_relpath(ffi_,data_); - if(!strcmp("fullpath",key)) - return l::file_fullpath(ffi_,data_); - if(!strcmp("allpaths",key)) - return l::file_allpaths(ffi_,data_); - - return -ENOATTR; - } - - static - bool - is_mergerfs_ioctl_cmd(const unsigned long cmd_) - { - return (_IOC_TYPE(cmd_) == IOCTL_APP_TYPE); - } - - static - bool - is_btrfs_ioctl_cmd(const unsigned long cmd_) - { - return (_IOC_TYPE(cmd_) == BTRFS_IOCTL_MAGIC); - } - - static - int - ioctl_custom(const fuse_file_info_t *ffi_, - unsigned long cmd_, - void *data_) - { - switch(cmd_) - { - case IOCTL_FILE_INFO: - return l::file_info(ffi_,data_); - case IOCTL_GC: - fuse_gc(); - return 0; - case IOCTL_GC1: - fuse_gc1(); - return 0; - case IOCTL_INVALIDATE_ALL_NODES: - fuse_invalidate_all_nodes(); - return 0; - case IOCTL_INVALIDATE_GID_CACHE: - GIDCache::invalidate_all(); - break; - } +static +int +_ioctl_dir(const fuse_file_info_t *ffi_, + const uint32_t cmd_, + void *data_, + uint32_t *out_bufsz_) +{ + Config::Read cfg; + DirInfo *di = reinterpret_cast(ffi_->fh); + const fuse_context *fc = fuse_get_context(); + const ugid::Set ugid(fc->uid,fc->gid); + + return ::_ioctl_dir_base(cfg->func.open.policy, + cfg->branches, + di->fusepath.c_str(), + cmd_, + data_, + out_bufsz_); +} - return -ENOTTY; - } +static +bool +_is_btrfs_ioctl_cmd(const unsigned long cmd_) +{ + return (_IOC_TYPE(cmd_) == BTRFS_IOCTL_MAGIC); } -namespace FUSE + +int +FUSE::ioctl(const fuse_file_info_t *ffi_, + unsigned long cmd_, + void *arg_, + unsigned int flags_, + void *data_, + uint32_t *out_bufsz_) { - int - ioctl(const fuse_file_info_t *ffi_, - unsigned long cmd_, - void *arg_, - unsigned int flags_, - void *data_, - uint32_t *out_bufsz_) - { - if(l::is_btrfs_ioctl_cmd(cmd_)) - return -ENOTTY; - if(l::is_mergerfs_ioctl_cmd(cmd_)) - return l::ioctl_custom(ffi_,cmd_,data_); - - if(flags_ & FUSE_IOCTL_DIR) - return l::ioctl_dir(ffi_,cmd_,data_,out_bufsz_); - - return l::ioctl_file(ffi_,cmd_,data_,out_bufsz_); - } + if(::_is_btrfs_ioctl_cmd(cmd_)) + return -ENOTTY; + + if(flags_ & FUSE_IOCTL_DIR) + return ::_ioctl_dir(ffi_,cmd_,data_,out_bufsz_); + + return ::_ioctl_file(ffi_,cmd_,data_,out_bufsz_); } diff --git a/src/fuse_listxattr.cpp b/src/fuse_listxattr.cpp index 2cc65c3f..701c1d24 100644 --- a/src/fuse_listxattr.cpp +++ b/src/fuse_listxattr.cpp @@ -14,98 +14,136 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "category.hpp" +#include "fuse_listxattr.hpp" + #include "config.hpp" #include "errno.hpp" #include "fs_llistxattr.hpp" -#include "fs_path.hpp" #include "ugid.hpp" #include "xattr.hpp" #include "fuse.h" +#include #include -#include +#include + +static +ssize_t +_listxattr_size(const std::vector &branches_, + const char *fusepath_) +{ + ssize_t rv; + ssize_t size; + std::string fullpath; + + size = 0; + for(const auto branch : branches_) + { + fullpath = fs::path::make(branch->path,fusepath_); + + rv = fs::llistxattr(fullpath,NULL,0); + if(rv < 0) + continue; + + size += rv; + } -using std::string; + return rv; +} +static +ssize_t +_listxattr(const std::vector &branches_, + const char *fusepath_, + char *list_, + size_t size_) +{ + ssize_t rv; + ssize_t size; + std::string fullpath; + std::optional err; + + if(size_ == 0) + return ::_listxattr_size(branches_,fusepath_); + + size = 0; + err = -ENOENT; + for(const auto branch : branches_) + { + fullpath = fs::path::make(branch->path,fusepath_); + + rv = fs::llistxattr(fullpath,list_,size_); + if(rv == -ERANGE) + return -ERANGE; + if(rv < 0) + { + if(!err.has_value()) + err = rv; + continue; + } + + err = 0; + list_ += rv; + size_ -= rv; + size += rv; + } + + if(err < 0) + return err.value(); + + return size; +} -namespace l +static +int +_listxattr(const Policy::Search &searchFunc_, + const Branches &ibranches_, + const char *fusepath_, + char *list_, + const size_t size_) { - static - int - listxattr_controlfile(Config::Read &cfg_, - char *list_, - const size_t size_) - { - string xattrs; - - cfg_->keys_xattr(xattrs); - if(size_ == 0) - return xattrs.size(); - - if(size_ < xattrs.size()) - return -ERANGE; - - memcpy(list_,xattrs.c_str(),xattrs.size()); - - return xattrs.size(); - } - - static - int - listxattr(const Policy::Search &searchFunc_, - const Branches &ibranches_, - const char *fusepath_, - char *list_, - const size_t size_) - { - int rv; - std::string fullpath; - std::vector obranches; - - rv = searchFunc_(ibranches_,fusepath_,obranches); - if(rv == -1) - return -errno; - - fullpath = fs::path::make(obranches[0]->path,fusepath_); - - rv = fs::llistxattr(fullpath,list_,size_); - - return ((rv == -1) ? -errno : rv); - } + int rv; + std::string fullpath; + std::vector obranches; + + rv = searchFunc_(ibranches_,fusepath_,obranches); + if(rv == -1) + return -errno; + + if(size_ == 0) + return ::_listxattr_size(obranches,fusepath_); + + return ::_listxattr(obranches,fusepath_,list_,size_); } -namespace FUSE +int +FUSE::listxattr(const char *fusepath_, + char *list_, + size_t size_) { - int - listxattr(const char *fusepath_, - char *list_, - size_t size_) - { - Config::Read cfg; - - if(fusepath_ == CONTROLFILE) - return l::listxattr_controlfile(cfg,list_,size_); - - switch(cfg->xattr) - { - case XAttr::ENUM::PASSTHROUGH: - break; - case XAttr::ENUM::NOATTR: - return 0; - case XAttr::ENUM::NOSYS: - return -ENOSYS; - } - - const fuse_context *fc = fuse_get_context(); - const ugid::Set ugid(fc->uid,fc->gid); - - return l::listxattr(cfg->func.listxattr.policy, - cfg->branches, - fusepath_, - list_, - size_); - } + Config::Read cfg; + + if(Config::is_ctrl_file(fusepath_)) + return cfg->keys_listxattr(list_,size_); + + switch(cfg->xattr) + { + case XAttr::ENUM::PASSTHROUGH: + break; + case XAttr::ENUM::NOATTR: + return 0; + case XAttr::ENUM::NOSYS: + return -ENOSYS; + } + + const fuse_context *fc = fuse_get_context(); + const ugid::Set ugid(fc->uid,fc->gid); + + return ::_listxattr(cfg->func.listxattr.policy, + cfg->branches, + fusepath_, + list_, + size_); } diff --git a/src/fuse_listxattr.hpp b/src/fuse_listxattr.hpp index f06c32c8..45906614 100644 --- a/src/fuse_listxattr.hpp +++ b/src/fuse_listxattr.hpp @@ -16,6 +16,7 @@ #pragma once +#include namespace FUSE { diff --git a/src/fuse_open.cpp b/src/fuse_open.cpp index 78dd8d44..f5ad92e6 100644 --- a/src/fuse_open.cpp +++ b/src/fuse_open.cpp @@ -14,7 +14,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include "fs_read.hpp" +#include "fuse_open.hpp" #include "state.hpp" diff --git a/src/fuse_readdir.cpp b/src/fuse_readdir.cpp index fe4f0eea..c1fb9287 100644 --- a/src/fuse_readdir.cpp +++ b/src/fuse_readdir.cpp @@ -18,9 +18,17 @@ #include "fuse_readdir.hpp" -#include "config.hpp" #include "fuse_readdir_factory.hpp" +#include "dirinfo.hpp" +#include "fuse_dirents.h" + +#include "config.hpp" + +#include + +#include + /* The _initialized stuff is not pretty but easiest way to deal with the fact that mergerfs is doing arg parsing and setting up of things @@ -90,15 +98,40 @@ FUSE::ReadDir::from_string(std::string const &str_) return 0; } +static +int +_handle_ENOENT(const fuse_file_info_t *ffi_, + fuse_dirents_t *buf_) +{ + dirent de; + DirInfo *di = reinterpret_cast(ffi_->fh); + + if(di->fusepath != "/") + return -ENOENT; + + de = {0}; + + de.d_ino = 0; + de.d_off = 0; + de.d_type = DT_REG; + strcpy(de.d_name,"error: no valid mergerfs branch found, check config"); + de.d_reclen = sizeof(de); + + fuse_dirents_add(buf_,&de,::strlen(de.d_name)); + + return 0; +} + /* Yeah... if not initialized it will crash... ¯\_(ツ)_/¯ This will be resolved once initialization of internal objects and - handling of input is better seperated. + handling of input is better separated. */ int -FUSE::ReadDir::operator()(fuse_file_info_t const *ffi_, +FUSE::ReadDir::operator()(const fuse_file_info_t *ffi_, fuse_dirents_t *buf_) { + int rv; std::shared_ptr readdir; { @@ -106,5 +139,9 @@ FUSE::ReadDir::operator()(fuse_file_info_t const *ffi_, readdir = _readdir; } - return (*readdir)(ffi_,buf_); + rv = (*readdir)(ffi_,buf_); + if(rv == -ENOENT) + return ::_handle_ENOENT(ffi_,buf_); + + return rv; } diff --git a/src/fuse_removexattr.cpp b/src/fuse_removexattr.cpp index cec300dc..916c6bd3 100644 --- a/src/fuse_removexattr.cpp +++ b/src/fuse_removexattr.cpp @@ -101,7 +101,7 @@ namespace FUSE { Config::Read cfg; - if(fusepath_ == CONTROLFILE) + if(Config::is_ctrl_file(fusepath_)) return -ENOATTR; if(cfg->xattr.to_int()) diff --git a/src/fuse_setxattr.cpp b/src/fuse_setxattr.cpp index 5d1d4826..105c34d3 100644 --- a/src/fuse_setxattr.cpp +++ b/src/fuse_setxattr.cpp @@ -14,187 +14,215 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "fuse_setxattr.hpp" + #include "config.hpp" #include "errno.hpp" #include "fs_glob.hpp" #include "fs_lsetxattr.hpp" #include "fs_path.hpp" #include "fs_statvfs_cache.hpp" +#include "gidcache.hpp" #include "num.hpp" #include "policy_rv.hpp" #include "str.hpp" #include "ugid.hpp" +#include "syslog.hpp" #include "fuse.h" #include #include -#include +#include static const char SECURITY_CAPABILITY[] = "security.capability"; -using std::string; -using std::vector; - -namespace l +static +int +_setxattr_cmd_xattr(const std::string_view &attrname_, + const std::string_view &attrval_) { - static - bool - is_attrname_security_capability(const char *attrname_) - { - return (strcmp(attrname_,SECURITY_CAPABILITY) == 0); - } + std::string_view cmd; + + cmd = Config::prune_cmd_xattr(attrname_); + + SysLog::info("command requested: {}={}",cmd,attrval_); - static - int - setxattr_controlfile(const string &attrname_, - const string &attrval_, - const int flags_) - { - int rv; - string key; - Config::Write cfg; + if(cmd == "gc") + return (fuse_gc(),0); + if(cmd == "gc1") + return (fuse_gc1(),0); + if(cmd == "invalidate-all-nodes") + return (fuse_invalidate_all_nodes(),0); + if(cmd == "invalidate-gid-cache") + return (GIDCache::invalidate_all(),0); + if(cmd == "clear-gid-cache") + return (GIDCache::clear_all(),0); - if(!str::startswith(attrname_,"user.mergerfs.")) - return -ENOATTR; + return -ENOATTR; +} - key = &attrname_[14]; +static +bool +_is_attrname_security_capability(const char *attrname_) +{ + return str::eq(attrname_,SECURITY_CAPABILITY); +} - if(cfg->has_key(key) == false) - return -ENOATTR; +static +int +_setxattr_ctrl_file(const char *attrname_, + const char *attrval_, + size_t attrvalsize_, + const int flags_) +{ + int rv; + std::string key; + Config::Write cfg; - if((flags_ & XATTR_CREATE) == XATTR_CREATE) - return -EEXIST; + if(!Config::is_mergerfs_xattr(attrname_)) + return -ENOATTR; - rv = cfg->set(key,attrval_); - if(rv < 0) - return rv; + if(Config::is_cmd_xattr(attrname_)) + return ::_setxattr_cmd_xattr(attrname_, + std::string_view{attrval_,attrvalsize_}); - fs::statvfs_cache_timeout(cfg->cache_statfs); + key = Config::prune_ctrl_xattr(attrname_); + if(cfg->has_key(key) == false) + return -ENOATTR; + + if((flags_ & XATTR_CREATE) == XATTR_CREATE) + return -EEXIST; + + rv = cfg->set(key,attrval_); + if(rv < 0) return rv; - } - - static - void - setxattr_loop_core(const string &basepath_, - const char *fusepath_, - const char *attrname_, - const char *attrval_, - const size_t attrvalsize_, - const int flags_, - PolicyRV *prv_) - { - string fullpath; - - fullpath = fs::path::make(basepath_,fusepath_); - - errno = 0; - fs::lsetxattr(fullpath,attrname_,attrval_,attrvalsize_,flags_); - - prv_->insert(errno,basepath_); - } - - static - void - setxattr_loop(const std::vector &branches_, - const char *fusepath_, - const char *attrname_, - const char *attrval_, - const size_t attrvalsize_, - const int flags_, - PolicyRV *prv_) - { - for(auto &branch : branches_) - { - l::setxattr_loop_core(branch->path, - fusepath_, - attrname_, - attrval_,attrvalsize_, - flags_, - prv_); - } - } - - static - int - setxattr(const Policy::Action &setxattrPolicy_, - const Policy::Search &getxattrPolicy_, - const Branches &branches_, - const char *fusepath_, - const char *attrname_, - const char *attrval_, - const size_t attrvalsize_, - const int flags_) - { - int rv; - PolicyRV prv; - std::vector branches; - - rv = setxattrPolicy_(branches_,fusepath_,branches); - if(rv == -1) - return -errno; - - l::setxattr_loop(branches,fusepath_,attrname_,attrval_,attrvalsize_,flags_,&prv); - if(prv.errors.empty()) - return 0; - if(prv.successes.empty()) - return prv.errors[0].rv; - - branches.clear(); - rv = getxattrPolicy_(branches_,fusepath_,branches); - if(rv == -1) - return -errno; - - return prv.get_error(branches[0]->path); - } - - int - setxattr(const char *fusepath_, - const char *attrname_, - const char *attrval_, - size_t attrvalsize_, - int flags_) - { - Config::Read cfg; - - if((cfg->security_capability == false) && - l::is_attrname_security_capability(attrname_)) - return -ENOATTR; - - if(cfg->xattr.to_int()) - return -cfg->xattr.to_int(); - - const fuse_context *fc = fuse_get_context(); - const ugid::Set ugid(fc->uid,fc->gid); - - return l::setxattr(cfg->func.setxattr.policy, - cfg->func.getxattr.policy, - cfg->branches, - fusepath_, - attrname_, - attrval_, - attrvalsize_, - flags_); - } + + fs::statvfs_cache_timeout(cfg->cache_statfs); + + return rv; } -namespace FUSE +static +void +_setxattr_loop_core(const std::string &basepath_, + const char *fusepath_, + const char *attrname_, + const char *attrval_, + const size_t attrvalsize_, + const int flags_, + PolicyRV *prv_) { - int - setxattr(const char *fusepath_, - const char *attrname_, - const char *attrval_, - size_t attrvalsize_, - int flags_) - { - if(fusepath_ == CONTROLFILE) - return l::setxattr_controlfile(attrname_, - string(attrval_,attrvalsize_), - flags_); - - return l::setxattr(fusepath_,attrname_,attrval_,attrvalsize_,flags_); - } + std::string fullpath; + + fullpath = fs::path::make(basepath_,fusepath_); + + errno = 0; + fs::lsetxattr(fullpath,attrname_,attrval_,attrvalsize_,flags_); + + prv_->insert(errno,basepath_); +} + +static +void +_setxattr_loop(const std::vector &branches_, + const char *fusepath_, + const char *attrname_, + const char *attrval_, + const size_t attrvalsize_, + const int flags_, + PolicyRV *prv_) +{ + for(auto &branch : branches_) + { + ::_setxattr_loop_core(branch->path, + fusepath_, + attrname_, + attrval_,attrvalsize_, + flags_, + prv_); + } +} + +static +int +_setxattr(const Policy::Action &setxattrPolicy_, + const Policy::Search &getxattrPolicy_, + const Branches &branches_, + const char *fusepath_, + const char *attrname_, + const char *attrval_, + const size_t attrvalsize_, + const int flags_) +{ + int rv; + PolicyRV prv; + std::vector branches; + + rv = setxattrPolicy_(branches_,fusepath_,branches); + if(rv == -1) + return -errno; + + ::_setxattr_loop(branches,fusepath_,attrname_,attrval_,attrvalsize_,flags_,&prv); + if(prv.errors.empty()) + return 0; + if(prv.successes.empty()) + return prv.errors[0].rv; + + branches.clear(); + rv = getxattrPolicy_(branches_,fusepath_,branches); + if(rv == -1) + return -errno; + + return prv.get_error(branches[0]->path); +} + +static +int +_setxattr(const char *fusepath_, + const char *attrname_, + const char *attrval_, + size_t attrvalsize_, + int flags_) +{ + Config::Read cfg; + + if((cfg->security_capability == false) && + ::_is_attrname_security_capability(attrname_)) + return -ENOATTR; + + if(cfg->xattr.to_int()) + return -cfg->xattr.to_int(); + + const fuse_context *fc = fuse_get_context(); + const ugid::Set ugid(fc->uid,fc->gid); + + return ::_setxattr(cfg->func.setxattr.policy, + cfg->func.getxattr.policy, + cfg->branches, + fusepath_, + attrname_, + attrval_, + attrvalsize_, + flags_); +} + + +int +FUSE::setxattr(const char *fusepath_, + const char *attrname_, + const char *attrval_, + size_t attrvalsize_, + int flags_) +{ + if(Config::is_ctrl_file(fusepath_)) + return ::_setxattr_ctrl_file(attrname_, + attrval_, + attrvalsize_, + flags_); + + return ::_setxattr(fusepath_,attrname_,attrval_,attrvalsize_,flags_); } diff --git a/src/fuse_setxattr.hpp b/src/fuse_setxattr.hpp index 6ae1f52e..0f373a62 100644 --- a/src/fuse_setxattr.hpp +++ b/src/fuse_setxattr.hpp @@ -16,6 +16,8 @@ #pragma once +#include + namespace FUSE { diff --git a/src/fuse_statx_supported.icpp b/src/fuse_statx_supported.icpp index 84726a68..9f90e176 100644 --- a/src/fuse_statx_supported.icpp +++ b/src/fuse_statx_supported.icpp @@ -1,3 +1,22 @@ +/* + ISC License + + Copyright (c) 2025, 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 "fuse_statx.hpp" #include "config.hpp" @@ -6,6 +25,7 @@ #include "fs_inode.hpp" #include "fs_path.hpp" #include "fs_statx.hpp" +#include "str.hpp" #include "symlinkify.hpp" #include "ugid.hpp" @@ -52,6 +72,24 @@ _set_stat_if_leads_to_reg(const std::string &path_, return; } +static +int +_statx_fake_root(struct fuse_statx *st_) +{ + st_->ino = 0; + st_->mode = (S_IFDIR|S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); + st_->nlink = 2; + st_->uid = 0; + st_->gid = 0; + st_->size = 0; + st_->blocks = 0; + st_->atime.tv_sec = 0; + st_->mtime.tv_sec = 0; + st_->ctime.tv_sec = 0; + + return 0; +} + static int _statx_controlfile(struct fuse_statx *st_) @@ -151,6 +189,8 @@ _statx(const char *fusepath_, cfg->symlinkify, cfg->symlinkify_timeout, cfg->follow_symlinks); + if((rv < 0) && Config::is_rootdir(fusepath_)) + return ::_statx_fake_root(st_); timeout_->entry = ((rv >= 0) ? cfg->cache_entry : @@ -167,7 +207,7 @@ FUSE::statx(const char *fusepath_, struct fuse_statx *st_, fuse_timeouts_t *timeout_) { - if(fusepath_ == CONTROLFILE) + if(Config::is_ctrl_file(fusepath_)) return ::_statx_controlfile(st_); return ::_statx(fusepath_,flags_|AT_STATX_DONT_SYNC,mask_,st_,timeout_); diff --git a/src/gidcache.cpp b/src/gidcache.cpp index df0fdd69..4bd0d1da 100644 --- a/src/gidcache.cpp +++ b/src/gidcache.cpp @@ -16,6 +16,8 @@ #include "gidcache.hpp" +#include "syslog.hpp" + #include #include #include @@ -130,27 +132,47 @@ GIDCache::initgroups(const uid_t uid_, void GIDCache::invalidate_all() { + size_t size; + + size = _records.size(); _records.visit_all([](auto &x) { x.second.last_update = 0; }); + + SysLog::info("gid cache invalidated, {} entries",size); } void GIDCache::clear_all() { + size_t size; + + size = _records.size(); _records.clear(); + + SysLog::info("gid cache cleared, {} entries",size); } void GIDCache::clear_unused() { + int erased = 0; time_t now = ::time(NULL); auto erase_func = - [=](auto &x) + [now,&erased](auto &x) { - return ((now - x.second.last_update) > GIDCache::remove_timeout); + bool should_erase; + time_t time_delta; + + time_delta = (now - x.second.last_update); + should_erase = (time_delta > GIDCache::remove_timeout); + erased += should_erase; + + return should_erase; }; _records.erase_if(erase_func); + + SysLog::info("cleared {} unused gid cache entries",erased); } diff --git a/src/gidcache.hpp b/src/gidcache.hpp index f23ac212..957422cc 100644 --- a/src/gidcache.hpp +++ b/src/gidcache.hpp @@ -21,20 +21,8 @@ #include #include -#include #include -#define MAXGIDS 32 -#define MAXRECS 256 - -// GIDCache is a global, per thread cache of uid to gid + supplemental -// groups mapping for use when threads change credentials. This is -// needed due to the high cost of querying such information. The cache -// instance should always be thread local and live the lifetime of the -// app. The constructor will register the instance so they can each be -// told to invalidate the cache on demand. A second instance on the -// same thread will cause an assert to be triggered. - struct GIDRecord { std::vector gids; diff --git a/src/mergerfs.cpp b/src/mergerfs.cpp index c7ffdcb0..d66e88d4 100644 --- a/src/mergerfs.cpp +++ b/src/mergerfs.cpp @@ -237,17 +237,17 @@ namespace l void usr1_signal_handler(int signal_) { - SysLog::info("Received SIGUSR1 - invalidating all nodes"); - fuse_invalidate_all_nodes(); + // SysLog::info("Received SIGUSR1 - invalidating all nodes"); + // fuse_invalidate_all_nodes(); } static void usr2_signal_handler(int signal_) { - SysLog::info("Received SIGUSR2 - triggering thorough gc"); - fuse_gc(); - GIDCache::clear_all(); + // SysLog::info("Received SIGUSR2 - triggering thorough gc"); + // fuse_gc(); + // GIDCache::clear_all(); } static @@ -284,8 +284,6 @@ namespace l fuse_operations ops; SysLog::open(); - SysLog::info("mergerfs v{} started",MERGERFS_VERSION); - SysLog::info("Go to https://trapexit.github.io/mergerfs/support for support"); memset(&ops,0,sizeof(fuse_operations)); @@ -309,6 +307,8 @@ namespace l return 1; } + SysLog::info("mergerfs v{} started",MERGERFS_VERSION); + SysLog::info("Go to https://trapexit.github.io/mergerfs/support for support"); l::warn_if_not_root(); MaintenanceThread::push_job([](int count_) diff --git a/src/mergerfs_api.cpp b/src/mergerfs_api.cpp index 3971a138..15630243 100644 --- a/src/mergerfs_api.cpp +++ b/src/mergerfs_api.cpp @@ -1,68 +1,71 @@ #include "mergerfs_api.hpp" -#include "fs_close.hpp" -#include "fs_ioctl.hpp" -#include "fs_open.hpp" -#include "mergerfs_ioctl.hpp" #include "fs_lgetxattr.hpp" - #include "str.hpp" #include "scope_guard.hpp" -#include +#include +#include + +typedef std::array mfs_api_buf_t; +static int -mergerfs::api::allpaths(const std::string &input_path_, - std::vector &output_paths_) +_lgetxattr(const std::string &input_path_, + const std::string &key_, + std::string &value_) { int rv; - IOCTL_BUF buf; + std::string key; + mfs_api_buf_t buf; - rv = fs::lgetxattr(input_path_,"user.mergerfs.allpaths",buf,sizeof(buf)); + key = "user.mergerfs." + key_; + rv = fs::lgetxattr(input_path_,key,buf.data(),buf.size()); if(rv < 0) return rv; - str::split_on_null(buf,rv,&output_paths_); + value_.clear(); + value_.reserve(rv); + value_.append(buf.data(),(size_t)rv); - return 0; + return rv; } int -_lgetxattr(const std::string &input_path_, - const std::string &key_, - std::string &value_) +mergerfs::api::allpaths(const std::string &input_path_, + std::vector &output_paths_) { int rv; - IOCTL_BUF buf; + std::string val; - rv = fs::lgetxattr(input_path_,key_,buf,sizeof(buf)); + rv = ::_lgetxattr(input_path_,"allpaths",val); if(rv < 0) return rv; - value_ = buf; + str::split_on_null(val.data(),val.size(),&output_paths_); - return rv; + return 0; } int mergerfs::api::basepath(const std::string &input_path_, std::string &basepath_) { - return ::_lgetxattr(input_path_,"user.mergerfs.basepath",basepath_); + return ::_lgetxattr(input_path_,"basepath",basepath_); } int mergerfs::api::relpath(const std::string &input_path_, std::string &relpath_) { - return ::_lgetxattr(input_path_,"user.mergerfs.relpath",relpath_); + return ::_lgetxattr(input_path_,"relpath",relpath_); } int mergerfs::api::fullpath(const std::string &input_path_, std::string &fullpath_) { - return ::_lgetxattr(input_path_,"user.mergerfs.fullpath",fullpath_); + return ::_lgetxattr(input_path_,"fullpath",fullpath_); } diff --git a/src/mergerfs_collect_info.cpp b/src/mergerfs_collect_info.cpp index a3a41843..4b5ccc6d 100644 --- a/src/mergerfs_collect_info.cpp +++ b/src/mergerfs_collect_info.cpp @@ -6,6 +6,7 @@ #include "CLI11.hpp" #include "fmt/core.h" +#include "fmt/ranges.h" #include "scope_guard.hpp" #include "subprocess.hpp" @@ -27,6 +28,29 @@ _write_str(const std::string &output_, ::fwrite(str_.c_str(),1,str_.size(),f); } +template +static +void +_run(const ARGS &args_, + const std::string &output_) +{ + std::string hdr; + + hdr = fmt::format("=== {}\n",fmt::join(args_," ")); + try + { + _write_str(output_,hdr); + subprocess::call(args_, + subprocess::output{output_.c_str()}); + } + catch(...) + { + ::_write_str(output_,"error: command failed to run\n"); + } + + _write_str(output_,"\n\n"); +} + static void _lsblk(const std::string &output_) @@ -38,8 +62,7 @@ _lsblk(const std::string &output_) "-o","NAME,FSTYPE,FSSIZE,SIZE,MOUNTPOINTS,RM,RO,ROTA" }; - subprocess::call(args, - subprocess::output{output_.c_str()}); + ::_run(args,output_); } static @@ -52,8 +75,7 @@ _mounts(const std::string &output_) "/proc/mounts" }; - subprocess::call(args, - subprocess::output{output_.c_str()}); + ::_run(args,output_); } static @@ -72,8 +94,7 @@ _mount_point_stats(const std::string &output_) for(const auto &path : allpaths) { auto args = {"stat",path.c_str()}; - subprocess::call(args, - subprocess::output{output_.c_str()}); + ::_run(args,output_); } } } @@ -88,14 +109,7 @@ _mergerfs_version(const std::string &output_) "--version" }; - try - { - subprocess::call(args, - subprocess::output{output_.c_str()}); - } - catch(...) - { - } + ::_run(args,output_); } static @@ -108,14 +122,7 @@ _uname(const std::string &output_) "-a" }; - try - { - subprocess::call(args, - subprocess::output{output_.c_str()}); - } - catch(...) - { - } + ::_run(args,output_); } static @@ -128,14 +135,7 @@ _lsb_release(const std::string &output_) "-a" }; - try - { - subprocess::call(args, - subprocess::output{output_.c_str()}); - } - catch(...) - { - } + ::_run(args,output_); } static @@ -148,14 +148,7 @@ _df(const std::string &output_) "-h" }; - try - { - subprocess::call(args, - subprocess::output{output_.c_str()}); - } - catch(...) - { - } + ::_run(args,output_); } static @@ -168,14 +161,7 @@ _fstab(const std::string &output_) "/etc/fstab" }; - try - { - subprocess::call(args, - subprocess::output{output_.c_str()}); - } - catch(...) - { - } + ::_run(args,output_); } @@ -202,21 +188,13 @@ mergerfs::collect_info::main(int argc_, fmt::print("* Please have mergerfs mounted before running this tool.\n"); fs::unlink(output_filepath); - ::_write_str(output_filepath,"::mergerfs --version::\n"); ::_mergerfs_version(output_filepath); - ::_write_str(output_filepath,"\n::uname -a::\n"); ::_uname(output_filepath); - ::_write_str(output_filepath,"\n::lsb_release -a::\n"); ::_lsb_release(output_filepath); - ::_write_str(output_filepath,"\n::df -h::\n"); ::_df(output_filepath); - ::_write_str(output_filepath,"\n::lsblk::\n"); ::_lsblk(output_filepath); - ::_write_str(output_filepath,"\n::cat /proc/mounts::\n"); ::_mounts(output_filepath); - ::_write_str(output_filepath,"\n::mount point stats::\n"); ::_mount_point_stats(output_filepath); - ::_write_str(output_filepath,"\n::cat /etc/fstab::\n"); ::_fstab(output_filepath); fmt::print("* Upload the following file to your" diff --git a/src/mergerfs_ioctl.hpp b/src/mergerfs_ioctl.hpp index d071a6f3..bcad8d24 100644 --- a/src/mergerfs_ioctl.hpp +++ b/src/mergerfs_ioctl.hpp @@ -1,5 +1,7 @@ #pragma once +#include "int_types.h" + #include #ifndef _IOC_TYPE @@ -10,11 +12,16 @@ #define _IOC_SIZEBITS 14 #endif -#define IOCTL_BUF_SIZE ((1 << _IOC_SIZEBITS) - 1) -typedef char IOCTL_BUF[IOCTL_BUF_SIZE]; -#define IOCTL_APP_TYPE 0xDF -#define IOCTL_FILE_INFO _IOWR(IOCTL_APP_TYPE,0,IOCTL_BUF) -#define IOCTL_GC _IO(IOCTL_APP_TYPE,1) -#define IOCTL_GC1 _IO(IOCTL_APP_TYPE,2) -#define IOCTL_INVALIDATE_ALL_NODES _IO(IOCTL_APP_TYPE,3) -#define IOCTL_INVALIDATE_GID_CACHE _IO(IOCTL_APP_TYPE,4) +#define MERGERFS_IOCTL_BUF_SIZE ((1 << _IOC_SIZEBITS) - 1) + +#pragma pack(push,1) +struct mergerfs_ioctl_t +{ + u32 size; + char buf[MERGERFS_IOCTL_BUF_SIZE - sizeof(u32)]; +}; +#pragma pack(pop) + +#define MERGERFS_IOCTL_APP_TYPE 0xDF +#define MERGERFS_IOCTL_GET _IOWR(MERGERFS_IOCTL_APP_TYPE,0,mergerfs_ioctl_t) +#define MERGERFS_IOCTL_SET _IOWR(MERGERFS_IOCTL_APP_TYPE,1,mergerfs_ioctl_t) diff --git a/src/nonstd/string.hpp b/src/nonstd/string.hpp new file mode 100644 index 00000000..966128da --- /dev/null +++ b/src/nonstd/string.hpp @@ -0,0 +1,2311 @@ +// +// Copyright (c) 2021-2021 Martin Moene +// +// https://github.com/martinmoene/string-lite +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef NONSTD_STRING_LITE_HPP +#define NONSTD_STRING_LITE_HPP + +#define string_lite_MAJOR 0 +#define string_lite_MINOR 0 +#define string_lite_PATCH 0 + +#define string_lite_VERSION string_STRINGIFY(string_lite_MAJOR) "." string_STRINGIFY(string_lite_MINOR) "." string_STRINGIFY(string_lite_PATCH) + +#define string_STRINGIFY( x ) string_STRINGIFY_( x ) +#define string_STRINGIFY_( x ) #x + +// tweak header support: + +#ifdef __has_include +# if __has_include() +# include +# endif +#define string_HAVE_TWEAK_HEADER 1 +#else +#define string_HAVE_TWEAK_HEADER 0 +//# pragma message("string.hpp: Note: Tweak header not supported.") +#endif + +#ifdef __has_include +# if __has_include( ) +# define string_HAVE_STD_STRING_VIEW 1 +# else +# define string_HAVE_STD_STRING_VIEW 0 +# endif +#else +# define string_HAVE_STD_STRING_VIEW 0 +#endif + +// Control presence of exception handling (try and auto discover): + +#ifndef string_CONFIG_NO_EXCEPTIONS +# if defined(_MSC_VER) +# include // for _HAS_EXCEPTIONS +# endif +# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS) +# define string_CONFIG_NO_EXCEPTIONS 0 +# else +# define string_CONFIG_NO_EXCEPTIONS 1 +# endif +#endif + +// TODO Switch between various versions of string_view: +// - minimal version: nonstd::string::string_view +// - lite version: nonstd::string_view +// - std version: std::string_view + +#define string_CONFIG_SELECT_STRING_VIEW_INTERNAL 1 +#define string_CONFIG_SELECT_STRING_VIEW_NONSTD 2 +#define string_CONFIG_SELECT_STRING_VIEW_STD 3 + +#ifndef string_CONFIG_SELECT_STRING_VIEW +# define string_CONFIG_SELECT_STRING_VIEW string_CONFIG_SELECT_STRING_VIEW_INTERNAL +#endif + +#if string_CONFIG_SELECT_STRING_VIEW==string_CONFIG_SELECT_STRING_VIEW_STD && !string_HAVE_STD_STRING_VIEW +# error string-lite: std::string_view selected but is not available: C++17? +#elif 0 // string_CONFIG_SELECT_STRING_VIEW==string_CONFIG_SELECT_STRING_VIEW_NONSTD && !defined(NONSTD_SV_LITE_H_INCLUDED) +# error string-lite: string-view-lite selected but is not available: nonstd/string_view.hpp included before nonstd/string.hpp? +#endif + +// C++ language version detection (C++23 is speculative): +// Note: VC14.0/1900 (VS2015) lacks too much from C++14. + +#ifndef string_CPLUSPLUS +# if defined(_MSVC_LANG ) && !defined(__clang__) +# define string_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) +# else +# define string_CPLUSPLUS __cplusplus +# endif +#endif + +#define string_CPP98_OR_GREATER ( string_CPLUSPLUS >= 199711L ) +#define string_CPP11_OR_GREATER ( string_CPLUSPLUS >= 201103L ) +#define string_CPP14_OR_GREATER ( string_CPLUSPLUS >= 201402L ) +#define string_CPP17_OR_GREATER ( string_CPLUSPLUS >= 201703L ) +#define string_CPP20_OR_GREATER ( string_CPLUSPLUS >= 202002L ) +#define string_CPP23_OR_GREATER ( string_CPLUSPLUS >= 202300L ) + +// MSVC version: + +#if defined(_MSC_VER ) && !defined(__clang__) +# define string_COMPILER_MSVC_VER (_MSC_VER ) +# define string_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) +# define string_COMPILER_MSVC_VERSION_FULL (_MSC_VER - 100 * ( 5 + (_MSC_VER < 1900 ) ) ) +#else +# define string_COMPILER_MSVC_VER 0 +# define string_COMPILER_MSVC_VERSION 0 +# define string_COMPILER_MSVC_VERSION_FULL 0 +#endif + +// clang version: + +#define string_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) + +#if defined( __apple_build_version__ ) +# define string_COMPILER_APPLECLANG_VERSION string_COMPILER_VERSION( __clang_major__, __clang_minor__, __clang_patchlevel__ ) +# define string_COMPILER_CLANG_VERSION 0 +#elif defined( __clang__ ) +# define string_COMPILER_APPLECLANG_VERSION 0 +# define string_COMPILER_CLANG_VERSION string_COMPILER_VERSION( __clang_major__, __clang_minor__, __clang_patchlevel__ ) +#else +# define string_COMPILER_APPLECLANG_VERSION 0 +# define string_COMPILER_CLANG_VERSION 0 +#endif + +// GNUC version: + +#if defined(__GNUC__) && !defined(__clang__) +# define string_COMPILER_GNUC_VERSION string_COMPILER_VERSION( __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__ ) +#else +# define string_COMPILER_GNUC_VERSION 0 +#endif + +// half-open range [lo..hi): +#define string_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) + +// Presence of language and library features: + +#define string_CPP11_100 (string_CPP11_OR_GREATER || string_COMPILER_MSVC_VER >= 1600) +#define string_CPP11_110 (string_CPP11_OR_GREATER || string_COMPILER_MSVC_VER >= 1700) +#define string_CPP11_120 (string_CPP11_OR_GREATER || string_COMPILER_MSVC_VER >= 1800) +#define string_CPP11_140 (string_CPP11_OR_GREATER || string_COMPILER_MSVC_VER >= 1900) +#define string_CPP11_141 (string_CPP11_OR_GREATER || string_COMPILER_MSVC_VER >= 1910) + +#define string_CPP11_000 (string_CPP11_OR_GREATER) + +#define string_CPP14_000 (string_CPP14_OR_GREATER) +#define string_CPP14_120 (string_CPP14_OR_GREATER || string_COMPILER_MSVC_VER >= 1800) + +#define string_CPP17_000 (string_CPP17_OR_GREATER) +#define string_CPP17_120 (string_CPP17_OR_GREATER || string_COMPILER_MSVC_VER >= 1800) +#define string_CPP17_140 (string_CPP17_OR_GREATER || string_COMPILER_MSVC_VER >= 1900) + +#define string_CPP20_000 (string_CPP20_OR_GREATER) + +// Presence of C++11 language features: + +#define string_HAVE_FREE_BEGIN string_CPP14_120 +#define string_HAVE_CONSTEXPR_11 (string_CPP11_000 && !string_BETWEEN(string_COMPILER_MSVC_VER, 1, 1910)) +#define string_HAVE_NOEXCEPT string_CPP11_140 +#define string_HAVE_NULLPTR string_CPP11_100 +#define string_HAVE_DEFAULT_FN_TPL_ARGS string_CPP11_120 +#define string_HAVE_EXPLICIT_CONVERSION string_CPP11_120 +#define string_HAVE_CHAR16_T string_CPP11_000 +#define string_HAVE_CHAR32_T string_HAVE_CHAR16_T + +// Presence of C++14 language features: + +#define string_HAVE_CONSTEXPR_14 string_CPP14_000 +#define string_HAVE_FREE_BEGIN string_CPP14_120 + +// Presence of C++17 language features: + +#define string_HAVE_FREE_SIZE string_CPP17_140 +#define string_HAVE_NODISCARD string_CPP17_000 + +// Presence of C++20 language features: + +#define string_HAVE_CHAR8_T string_CPP20_000 + +// Presence of C++ library features: + +#define string_HAVE_REGEX (string_CPP11_000 && !string_BETWEEN(string_COMPILER_GNUC_VERSION, 1, 490)) +#define string_HAVE_TYPE_TRAITS string_CPP11_110 + +// Usage of C++ language features: + +#if string_HAVE_CONSTEXPR_11 +# define string_constexpr constexpr +#else +# define string_constexpr /*constexpr*/ +#endif + +#if string_HAVE_CONSTEXPR_14 +# define string_constexpr14 constexpr +#else +# define string_constexpr14 /*constexpr*/ +#endif + +#if string_HAVE_EXPLICIT_CONVERSION +# define string_explicit explicit +#else +# define string_explicit /*explicit*/ +#endif + +#if string_HAVE_NOEXCEPT && !string_CONFIG_NO_EXCEPTIONS +# define string_noexcept noexcept +#else +# define string_noexcept /*noexcept*/ +#endif + +#if string_HAVE_NODISCARD +# define string_nodiscard [[nodiscard]] +#else +# define string_nodiscard /*[[nodiscard]]*/ +#endif + +#if string_HAVE_NULLPTR +# define string_nullptr nullptr +#else +# define string_nullptr NULL +#endif + +#if string_HAVE_EXPLICIT_CONVERSION +# define string_explicit_cv explicit +#else +# define string_explicit_cv /*explicit*/ +#endif + +#define string_HAS_ENABLE_IF_ (string_HAVE_TYPE_TRAITS && string_HAVE_DEFAULT_FN_TPL_ARGS) +#define string_TEST_STRING_CONTAINS (string_HAS_ENABLE_IF_ && !string_BETWEEN(string_COMPILER_MSVC_VER, 1, 1910)) +#define string_TEST_STRING_STARTS_WITH string_TEST_STRING_CONTAINS +#define string_TEST_STRING_ENDS_WITH string_TEST_STRING_CONTAINS + +// Method enabling (return type): + +#if string_HAVE_TYPE_TRAITS +# define string_ENABLE_IF_R_(VA, R) typename std::enable_if< (VA), R >::type +#else +# define string_ENABLE_IF_R_(VA, R) R +#endif + +// Method enabling (function template argument): + +#if string_HAS_ENABLE_IF_ + +// VS 2013 seems to have trouble with SFINAE for default non-type arguments: +# if !string_BETWEEN( string_COMPILER_MSVC_VER, 1, 1900 ) +# define string_ENABLE_IF_(VA) , typename std::enable_if< (VA), int >::type = 0 +# else +# define string_ENABLE_IF_(VA) , typename = typename std::enable_if< (VA), ::nonstd::string::detail::enabler >::type +# endif + +# define string_ENABLE_IF_HAS_METHOD_( type, method) string_ENABLE_IF_( string_HAS_METHOD_( type, method) ) +# define string_DISABLE_IF_HAS_METHOD_( type, method) string_ENABLE_IF_( !string_HAS_METHOD_( type, method) ) + +#else +# define string_ENABLE_IF_(VA) +# define string_ENABLE_IF_HAS_METHOD_( type, member) +# define string_DISABLE_IF_HAS_METHOD_(type, member) +#endif + +// Additional includes: + +#include +#include +#include +#include +#include +#include +#include + +#if string_HAVE_REGEX +# include +#endif + +#if ! string_CONFIG_NO_EXCEPTIONS +# include +#endif + +#if string_HAVE_TYPE_TRAITS +# include +#elif string_HAVE_TR1_TYPE_TRAITS +# include +#endif + +#if string_CONFIG_SELECT_STRING_VIEW == string_CONFIG_SELECT_STRING_VIEW_INTERNAL +// noop +#elif string_CONFIG_SELECT_STRING_VIEW == string_CONFIG_SELECT_STRING_VIEW_NONSTD +# define nssv_CONFIG_SELECT_STRING_VIEW nssv_STRING_VIEW_NONSTD +# include "nonstd/string_view.hpp" +#elif string_CONFIG_SELECT_STRING_VIEW == string_CONFIG_SELECT_STRING_VIEW_STD +# include +#endif + +// Method detection: + +#define string_HAS_METHOD_( T, M ) \ + ::nonstd::string::has_##M::value + +#if string_CPP11_OR_GREATER && !string_BETWEEN(string_COMPILER_GNUC_VERSION, 1, 500) + +# define string_MAKE_HAS_METHOD_( M ) \ + template< typename T > \ + using M##_t = decltype(std::declval().M()); \ + \ + template \ + using has_##M = std23::is_detected; + +#else // string_CPP11_OR_GREATER + +# define string_MAKE_HAS_METHOD_( M ) \ + template< typename T > \ + struct has_##M \ + { \ + typedef char yes[1]; \ + typedef char no[2]; \ + \ + template< typename U > \ + static yes & test( int (*)[sizeof(std98::declval().M(), 1)] ); \ + \ + template< typename U > \ + static no & test(...); \ + \ + static const bool value = sizeof( test(NULL) ) == sizeof(yes); \ + }; + +#endif + +// Type traits: +namespace nonstd { +namespace string { + +namespace std98 { + +template< typename T, T v > struct integral_constant { enum { value = v }; }; +typedef integral_constant< bool, true > true_type; +typedef integral_constant< bool, false > false_type; + +template< typename T, typename U > +struct is_same { enum dummy { value = false }; }; + +template< typename T > +struct is_same { enum dummy { value = true }; }; + +template< typename T > +T declval(); + +} // namespace std98 + +#if string_CPP11_OR_GREATER + +namespace std11 { + +template< bool B, typename T, typename F > +struct conditional { typedef T type; }; + +template< typename T, typename F > +struct conditional { typedef F type; }; + +template< typename T > struct remove_const { typedef T type; }; +template< typename T > struct remove_const { typedef T type; }; + +template< typename T > struct remove_volatile { typedef T type; }; +template< typename T > struct remove_volatile { typedef T type; }; + +template< typename T > struct remove_cv { typedef typename remove_volatile::type>::type type; }; + +// template< typename T > struct is_const : std98::false_type {}; +// template< typename T > struct is_const : std98::true_type {}; + +template< typename T > struct is_pointer_helper : std98::false_type {}; +template< typename T > struct is_pointer_helper : std98::true_type {}; +template< typename T > struct is_pointer : is_pointer_helper::type> {}; + +} // C++11 + +namespace std14 { + +template< bool B, typename T, typename F > +using conditional_t = typename std11::conditional::type; + +} // namespace c++14 + +namespace std17 { + +template< typename... > +using void_t = void; + +} // namespace c++17 + +namespace std20 { + +// type identity, to establish non-deduced contexts in template argument deduction: + +template< typename T > +struct type_identity +{ + typedef T type; +}; + +#if string_CPP11_100 + +struct identity +{ + template< typename T > + string_constexpr T && operator ()( T && arg ) const string_noexcept + { + return std::forward( arg ); + } +}; + +#endif // string_CPP11_100 + +} // namespace c++20 + +namespace std23 { +namespace detail { + +template< typename Default, typename AlwaysVoid, template class Op, typename... Args > +struct detector +{ + using value_t = std::false_type; + using type = Default; +}; + +template< typename Default, template class Op, typename... Args > +struct detector>, Op, Args...> +{ + using value_t = std::true_type; + using type = Op; +}; + +} // namespace detail + +struct nonesuch +{ + ~nonesuch() = delete; + nonesuch( nonesuch const & ) = delete; + void operator=( nonesuch const & ) = delete; +}; + +// pre-C+17 requires `class Op`: +template< template class Op, typename... Args > +using is_detected = typename detail::detector::value_t; + +} // std23 + +#endif // string_CPP11_OR_GREATER + +// for string_HAS_METHOD_: + +string_MAKE_HAS_METHOD_( begin ) +string_MAKE_HAS_METHOD_( clear ) +string_MAKE_HAS_METHOD_( contains ) +string_MAKE_HAS_METHOD_( empty ) +string_MAKE_HAS_METHOD_( starts_with ) +string_MAKE_HAS_METHOD_( ends_with ) +string_MAKE_HAS_METHOD_( replace ) + +// string-lite API functions: + +// Utilities: + +template< typename CharT > +string_nodiscard CharT nullchr() string_noexcept +{ + return 0; +} + +// free function min(): + +template< typename T > +inline T min( T a, typename std20::type_identity::type b ) +{ + return a < b ? a : b; +} + +// free function length(): + +template< typename Coll > +string_nodiscard size_t length( Coll const & coll ) +{ + return coll.length(); +} + +#if string_HAVE_FREE_SIZE + +using std::size; + +#else // string_HAVE_FREE_SIZE + +template< typename Cont > +string_nodiscard inline size_t size( Cont const & c ) +{ + return c.size(); +} + +#endif // string_HAVE_FREE_SIZE + +// nonstd size(C-string) + +// TODO Add char16_t, char32_t, wchar_t variations - size() + +string_nodiscard inline size_t size( char * s ) +{ + return strlen( s ); +} + +string_nodiscard inline size_t size( char const * s ) +{ + return strlen( s ); +} + +string_nodiscard inline size_t size( wchar_t * s ) +{ + return wcslen( s ); +} + +string_nodiscard inline size_t size( wchar_t const * s ) +{ + return wcslen( s ); +} + +#if string_HAVE_CHAR16_T +#endif + +// non-standard begin(), end() for char*: + +// TODO Add char16_t, char32_t, wchar_t variations - begin(), end() + +template< typename PCharT > +string_nodiscard inline +string_ENABLE_IF_R_(std11::is_pointer::value, PCharT) +begin( PCharT text ) +{ + return text; +} + +template< typename PCharT > +string_nodiscard inline +string_ENABLE_IF_R_(std11::is_pointer::value, PCharT) +end( PCharT text ) +{ + return text + size( text ); +} + +template< typename CharT > +string_nodiscard inline CharT const * +cbegin( CharT const * text ) +{ + return text; +} + +template< typename CharT > +string_nodiscard inline CharT const * +cend( CharT const * text ) +{ + return text + size( text ); +} + +// standard begin() and end(): + +#if string_HAVE_FREE_BEGIN + +using std::begin; +using std::cbegin; +using std::crbegin; +using std::end; +using std::cend; +using std::crend; + +#else // string_HAVE_FREE_BEGIN + +template< typename StringT > +string_nodiscard inline typename StringT::iterator begin( StringT & text ) +{ + return text.begin(); +} + +template< typename StringT > +string_nodiscard inline typename StringT::iterator end( StringT & text ) +{ + return text.end(); +} + +#if string_CPP11_000 + +template< typename StringT > +string_nodiscard inline typename StringT::const_iterator begin( StringT const & text ) +{ + return text.cbegin(); +} + +template< typename StringT > +string_nodiscard inline typename StringT::const_iterator end( StringT const & text ) +{ + return text.cend(); +} + +template< typename StringT > +string_nodiscard inline typename StringT::const_iterator cbegin( StringT const & text ) +{ + return text.cbegin(); +} + +template< typename StringT > +string_nodiscard inline typename StringT::const_iterator cend( StringT const & text ) +{ + return text.cend(); +} + +template< typename StringT > +string_nodiscard inline typename StringT::reverse_iterator rbegin( StringT const & text ) +{ + return text.rbegin(); +} + +template< typename StringT > +string_nodiscard inline typename StringT::reverse_iterator rend( StringT const & text ) +{ + return text.rend(); +} + +template< typename StringT > +string_nodiscard inline typename StringT::const_reverse_iterator crbegin( StringT const & text ) +{ + return text.crbegin(); +} + +template< typename StringT > +string_nodiscard inline typename StringT::const_reverse_iterator crend( StringT const & text ) +{ + return text.crend(); +} + +#else // string_CPP11_000 + +template< typename StringT > +string_nodiscard inline typename StringT::const_iterator cbegin( StringT const & text ) +{ + typedef typename StringT::const_iterator const_iterator; + return const_iterator( text.begin() ); +} + +template< typename StringT > +string_nodiscard inline typename StringT::const_iterator cend( StringT const & text ) +{ + typedef typename StringT::const_iterator const_iterator; + return const_iterator( text.end() ); +} + +template< typename StringT > +string_nodiscard inline typename StringT::const_reverse_iterator crbegin( StringT const & text ) +{ + typedef typename StringT::const_reverse_iterator const_reverse_iterator; + return const_reverse_iterator( text.rbegin() ); +} + +template< typename StringT > +string_nodiscard inline typename StringT::const_reverse_iterator crend( StringT const & text ) +{ + typedef typename StringT::const_reverse_iterator const_reverse_iterator; + return const_reverse_iterator( text.rend() ); +} + +#endif // string_CPP11_000 +#endif // string_HAVE_FREE_BEGIN + +// Minimal internal string_view for string algorithm library, when requested: + +#if string_CONFIG_SELECT_STRING_VIEW == string_CONFIG_SELECT_STRING_VIEW_INTERNAL + +template +< + class CharT, + class Traits = std::char_traits +> +class basic_string_view +{ +public: + // Member types: + + typedef Traits traits_type; + typedef CharT value_type; + + typedef CharT * pointer; + typedef CharT const * const_pointer; + typedef CharT & reference; + typedef CharT const & const_reference; + + typedef const_pointer iterator; + typedef const_pointer const_iterator; + typedef std::reverse_iterator< const_iterator > reverse_iterator; + typedef std::reverse_iterator< const_iterator > const_reverse_iterator; + + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + // 24.4.2.1 Construction and assignment: + + string_constexpr basic_string_view() string_noexcept + : data_( string_nullptr ) + , size_( 0 ) + {} + + string_constexpr basic_string_view( CharT const * s ) string_noexcept // non-standard noexcept + : data_( s ) + , size_( Traits::length(s) ) + {} + + string_constexpr basic_string_view( CharT const * s, size_type count ) string_noexcept // non-standard noexcept + : data_( s ) + , size_( count ) + {} + + string_constexpr basic_string_view( CharT const * b, CharT const * e ) string_noexcept // non-standard noexcept + : data_( b ) + , size_( e - b ) + {} + + string_constexpr basic_string_view( std::basic_string & s ) string_noexcept // non-standard noexcept + : data_( s.data() ) + , size_( s.size() ) + {} + + string_constexpr basic_string_view( std::basic_string const & s ) string_noexcept // non-standard noexcept + : data_( s.data() ) + , size_( s.size() ) + {} + +// #if string_HAVE_EXPLICIT_CONVERSION + + template< class Allocator > + string_explicit operator std::basic_string() const + { + return to_string( Allocator() ); + } + +// #endif // string_HAVE_EXPLICIT_CONVERSION + +#if string_CPP11_OR_GREATER + + template< class Allocator = std::allocator > + std::basic_string + to_string( Allocator const & a = Allocator() ) const + { + return std::basic_string( begin(), end(), a ); + } + +#else + + std::basic_string + to_string() const + { + return std::basic_string( begin(), end() ); + } + + template< class Allocator > + std::basic_string + to_string( Allocator const & a ) const + { + return std::basic_string( begin(), end(), a ); + } + +#endif // string_CPP11_OR_GREATER + + + string_constexpr14 size_type find( basic_string_view v, size_type pos = 0 ) const string_noexcept // (1) + { + return assert( v.size() == 0 || v.data() != string_nullptr ) + , pos >= size() + ? npos + : to_pos( std::search( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) ); + } + + string_constexpr14 size_type find( CharT c, size_type pos = 0 ) const string_noexcept // (2) + { + return find( basic_string_view( &c, 1 ), pos ); + } + + string_constexpr size_type find_first_of( basic_string_view v, size_type pos = 0 ) const string_noexcept // (1) + { + return pos >= size() + ? npos + : to_pos( std::find_first_of( cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq ) ); + } + + string_constexpr size_type size() const string_noexcept { return size_; } + string_constexpr size_type length() const string_noexcept { return size_; } + string_constexpr const_pointer data() const string_noexcept { return data_; } + + string_constexpr14 basic_string_view substr( size_type pos = 0, size_type n = npos ) const + { +#if string_CONFIG_NO_EXCEPTIONS + assert( pos <= size() ); +#else + if ( pos > size() ) + { + throw std::out_of_range("string_view::substr()"); + } +#endif + return basic_string_view( data() + pos, (std::min)( n, size() - pos ) ); + } + + string_constexpr const_iterator begin() const string_noexcept { return data_; } + string_constexpr const_iterator end() const string_noexcept { return data_ + size_; } + + string_constexpr const_iterator cbegin() const string_noexcept { return begin(); } + string_constexpr const_iterator cend() const string_noexcept { return end(); } + + string_constexpr const_reverse_iterator rbegin() const string_noexcept { return const_reverse_iterator( end() ); } + string_constexpr const_reverse_iterator rend() const string_noexcept { return const_reverse_iterator( begin() ); } + + string_constexpr const_reverse_iterator crbegin() const string_noexcept { return rbegin(); } + string_constexpr const_reverse_iterator crend() const string_noexcept { return rend(); } + + string_constexpr size_type to_pos( const_iterator it ) const + { + return it == cend() ? npos : size_type( it - cbegin() ); + } + + // Constants: + +#if string_CPP17_OR_GREATER + static string_constexpr size_type npos = size_type(-1); +#elif string_CPP11_OR_GREATER + enum : size_type { npos = size_type(-1) }; +#else + enum { npos = size_type(-1) }; +#endif + +private: + const_pointer data_; + size_type size_; +}; + +typedef basic_string_view string_view; +typedef basic_string_view wstring_view; + +#if string_HAVE_CHAR16_T + +typedef basic_string_view u16string_view; +typedef basic_string_view u32string_view; +#endif + +template< typename T > +string_nodiscard inline size_t size( basic_string_view const & sv ) +{ + return sv.size(); +} + +template< typename T > +string_nodiscard inline typename basic_string_view::const_reverse_iterator +crbegin( basic_string_view const & sv ) +{ + typedef typename basic_string_view::const_reverse_iterator const_reverse_iterator; + return const_reverse_iterator( sv.rbegin() ); +} + +template< typename T > +string_nodiscard inline typename basic_string_view::const_reverse_iterator +crend( basic_string_view const & sv ) +{ + typedef typename basic_string_view::const_reverse_iterator const_reverse_iterator; + return const_reverse_iterator( sv.rend() ); +} + +#elif string_CONFIG_SELECT_STRING_VIEW == string_CONFIG_SELECT_STRING_VIEW_NONSTD + +using nonstd::sv_lite::basic_string_view; +using nonstd::sv_lite::string_view; +using nonstd::sv_lite::wstring_view; + +#if nssv_HAVE_WCHAR16_T +using nonstd::sv_lite::u16string_view; +#endif +#if nssv_HAVE_WCHAR32_T +using nonstd::sv_lite::u32string_view; +#endif + +#elif string_CONFIG_SELECT_STRING_VIEW == string_CONFIG_SELECT_STRING_VIEW_STD + +using std::basic_string_view; +using std::string_view; +using std::wstring_view; +#if string_HAVE_CHAR16_T +using std::u16string_view; +using std::u32string_view; +#endif + +#endif // string_CONFIG_SELECT_STRING_VIEW_INTERNAL + +// Convert string_view to std::string: + +#if string_CONFIG_SELECT_STRING_VIEW != string_CONFIG_SELECT_STRING_VIEW_NONSTD + +template< class CharT, class Traits > +std::basic_string +to_string( basic_string_view v ) +{ + return std::basic_string( v.begin(), v.end() ); +} + +template< class CharT, class Traits, class Allocator > +std::basic_string +to_string( basic_string_view v, Allocator const & a ) +{ + return std::basic_string( v.begin(), v.end(), a ); +} + +#endif // string_CONFIG_SELECT_STRING_VIEW + +// to_identity() - let nonstd::string_view operate with pre-std::string_view std::string methods: + +template< class CharT > +CharT const * to_identity( CharT const * s ) +{ + return s; +} + +template< class CharT, class Traits, class Allocator > +std::basic_string +to_identity( std::basic_string const & s ) +{ + return s; +} + +#if string_CONFIG_SELECT_STRING_VIEW == string_CONFIG_SELECT_STRING_VIEW_STD + +template< class CharT, class Traits > +basic_string_view +to_identity( basic_string_view v ) +{ + return v; +} + +#else + +template< class CharT, class Traits > +std::basic_string +to_identity( basic_string_view v ) +{ + return to_string( v ); +} + +#endif // string_CONFIG_SELECT_STRING_VIEW + +// namespace detail: + +namespace detail { + +// for string_ENABLE_IF_(): + +/*enum*/ class enabler{}; + +template< typename CharT > +string_nodiscard inline CharT as_lowercase( CharT chr ) +{ + return std::tolower( chr, std::locale() ); +} + +template< typename CharT > +string_nodiscard inline CharT as_uppercase( CharT chr ) +{ + return std::toupper( chr, std::locale() ); +} + +// case conversion: + +// Note: serve both CharT* and StringT&: + +template< typename StringT, typename Fn > +StringT & to_case( StringT & text, Fn fn ) string_noexcept +{ + std::transform( + string::begin( text ), string::end( text ) + , string::begin( text ) + , fn + ); + + return text; +} + +// find_first(): + +template< typename StringT, typename SubT > +typename StringT::iterator find_first( StringT & text, SubT const & seek ) +{ + return std::search( + string::begin(text), string::end(text) + , string::cbegin(seek), string::cend(seek) + ); +} + +template< typename StringT, typename SubT > +typename StringT::const_iterator find_first( StringT const & text, SubT const & seek ) +{ + return std::search( + string::cbegin(text), string::cend(text) + , string::cbegin(seek), string::cend(seek) + ); +} + +// find_last(): + +template< typename StringIt, typename SubIt, typename PredicateT > +StringIt find_last( StringIt text_pos, StringIt text_end, SubIt seek_pos, SubIt seek_end, PredicateT compare ) +{ + if ( seek_pos == seek_end ) + return text_end; + + StringIt result = text_end; + + while ( true ) + { + StringIt new_result = std::search( text_pos, text_end, seek_pos, seek_end, compare ); + + if ( new_result == text_end ) + { + break; + } + else + { + result = new_result; + text_pos = result; + ++text_pos; + } + } + return result; +} + +template< typename StringT, typename SubT, typename PredicateT > +typename StringT::iterator find_last( StringT & text, SubT const & seek, PredicateT compare ) +{ + return detail::find_last( + string::begin(text), string::end(text) + , string::cbegin(seek), string::cend(seek) + , compare + ); +} + +template< typename StringT, typename SubT, typename PredicateT > +typename StringT::const_iterator find_last( StringT const & text, SubT const & seek, PredicateT compare ) +{ + return detail::find_last( + string::cbegin(text), string::cend(text) + , string::cbegin(seek), string::cend(seek) + , compare + ); +} + +// starts_with(): + +template< typename StringIt, typename SubIt, typename PredicateT > +bool starts_with( StringIt text_pos, StringIt text_end, SubIt seek_pos, SubIt seek_end, PredicateT compare ) +{ + for( ; text_pos != text_end && seek_pos != seek_end; ++text_pos, ++seek_pos ) + { + if( !compare( *text_pos, *seek_pos ) ) + return false; + } + + return seek_pos == seek_end; +} + +template< typename StringT, typename SubT, typename PredicateT > +bool starts_with( StringT const & text, SubT const & seek, PredicateT compare ) +{ + return detail::starts_with( + string::cbegin(text), string::cend(text) + , string::cbegin(seek), string::cend(seek) + , compare + ); +} + +// ends_with(): + +template< typename StringT, typename SubT, typename PredicateT > +bool ends_with( StringT const & text, SubT const & seek, PredicateT compare ) +{ + return detail::starts_with( + string::crbegin(text), string::crend(text) + , string::crbegin(seek), string::crend(seek) + , compare + ); +} + +// replace_all(): + +// TODO replace_all() - alg: + +template< typename StringIt, typename FromIt, typename ToIt, typename PredicateT > +bool replace_all +( + StringIt text_pos, StringIt text_end + , FromIt from_pos, FromIt from_end + , ToIt to_pos, ToIt to_end + , PredicateT compare +) +{ + return true; // error +} + +template< typename CharT, typename FromT, typename ToT > +std::basic_string & replace_all( std::basic_string & text, FromT const & from, ToT const & to ) string_noexcept +{ + for ( ;; ) + { + const size_t pos = text.find( from ); + + if ( pos == std::string::npos ) + return text; + + text.replace( pos, ::nonstd::string::size(from), to_identity(to) ); + } +} + +template< typename StringT, typename FromT, typename ToT > +StringT & replace_all( StringT & text, FromT const & from, ToT const & to ) string_noexcept +{ + typedef typename StringT::value_type A; + + (void) detail::replace_all( + string::begin(text), string::end(text) + , string::cbegin(from), string::cend(from) + , string::cbegin(to), string::cend(to) + , std::equal_to() + ); + + return text; +} + +// replace_first(): + +template< typename CharT, typename FromT, typename ToT > +std::basic_string & replace_first( std::basic_string & text, FromT const & from, ToT const & to ) string_noexcept +{ + const size_t pos = text.find( to_identity(from) ); + + if ( pos == std::string::npos ) + return text; + + return text.replace( pos, ::nonstd::string::size(from), to_identity(to) ); +} + +// replace_last(): + +template< typename CharT, typename FromT, typename ToT > +std::basic_string & +replace_last( std::basic_string & text, FromT const & from, ToT const & to ) string_noexcept +{ + const size_t pos = text.rfind( to_identity(from) ); + + if ( pos == std::string::npos ) + return text; + + return text.replace( pos, ::nonstd::string::size(from), to_identity(to) ); +} + +// append(): + +template< typename CharT, typename TailT > +string_constexpr CharT * +append( CharT * text, TailT const & tail ) string_noexcept +{ + return std::strcat( text, to_identity(tail) ); +} + +template< typename CharT, typename TailT > +string_constexpr std::basic_string & +append( std::basic_string & text, TailT const & tail ) string_noexcept +{ + return text.append( to_identity(tail) ); +} + +// TODO trim() - alg + +template< typename CharT, typename SetT > +string_constexpr14 CharT * +trim_left( CharT * text, SetT const * set ) string_noexcept +{ + // TODO trim() - strspn(CharT), adapt std::strspn to CharT + const int pos = std::strspn( text, set ); + + memmove( text, text + pos, 1 + size( text ) - pos ); + + return text; +} + +template< typename CharT, typename SetT > +string_constexpr std::basic_string & +trim_left( std::basic_string & text, SetT const & set ) string_noexcept +{ + return text.erase( 0, text.find_first_not_of( set ) ); +} + +template< typename StringT, typename SetT > +string_constexpr StringT & +trim_left( StringT & text, SetT const & /*set*/ ) string_noexcept +{ + // TODO trim() - make generic version + return text; +} + +template< typename CharT, typename SetT > +string_constexpr14 CharT * +trim_right( CharT * text, SetT const * set ) string_noexcept +{ + std::size_t length = size( text ); + + if ( !length ) + return text; + + char * end = text + length - 1; + + // TODO trim() - strspn(CharT), adapt std::strchr to CharT + while ( end >= text && std::strchr( set, *end ) ) + end--; + + *( end + 1 ) = '\0'; + + return text; +} + +template< typename CharT, typename SetT > +string_constexpr std::basic_string & +trim_right( std::basic_string & text, SetT const & set ) string_noexcept +{ + return text.erase( text.find_last_not_of( set ) + 1 ); +} + +template< typename StringT, typename SetT > +string_constexpr StringT & +trim_right( StringT & text, SetT const & /*set*/ ) string_noexcept +{ + // TODO trim() - make generic version + return text; +} + +template< typename CharT, typename SetT > +string_constexpr CharT * +trim( CharT * text, SetT const * set ) string_noexcept +{ + return trim_right( trim_left( text, set ), set); +} + +template< typename CharT, typename SetT > +string_constexpr std::basic_string & +trim( std::basic_string & text, SetT const & set ) string_noexcept +{ + return trim_right( trim_left( text, set ), set); +} + +template< typename StringT, typename SetT > +string_constexpr StringT & +trim( StringT & text, SetT const & set ) string_noexcept +{ + return trim_right( trim_left( text, set ), set); +} + +// TODO join() - alg + +// split(): + +template< typename CharT, typename Delimiter > +std::vector > +split(basic_string_view text, Delimiter delimiter) +{ + std::vector > result; + + size_t pos = 0; + + for( basic_string_view sv = delimiter(text, pos); sv.cbegin() != text.cend(); sv = delimiter(text, pos) ) + { + result.push_back(sv); + pos = (sv.end() - text.begin()) + length(delimiter); + } + + return result; +} + +} // namespace detail + +// Observers: + +// is_empty(): + +template< typename CharT > +string_nodiscard bool is_empty( CharT const * cp ) string_noexcept +{ + assert( cp != string_nullptr ); + return *cp == nullchr(); +} + +template< typename StringT + string_ENABLE_IF_HAS_METHOD_(StringT, empty) +> +string_nodiscard bool is_empty( StringT const & text ) string_noexcept +{ + return text.empty(); +} + +// find_first(): + +template< typename StringT, typename SubT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +string_constexpr typename StringT::iterator find_first( StringT & text, SubT const & seek ) +{ + return detail::find_first( text, seek ); +} + +template< typename StringT, typename SubT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +string_constexpr typename StringT::const_iterator find_first( StringT const & text, SubT const & seek ) +{ + return detail::find_first( text, seek ); +} + +template< typename CharT, typename SubT > +string_constexpr CharT * find_first( CharT * text, SubT const & seek ) +{ + return detail::find_first( text, seek ); +} + +// find_last(): + +template< typename StringT, typename SubT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +string_constexpr typename StringT::iterator find_last( StringT & text, SubT const & seek ) +{ + typedef typename StringT::value_type A; + + return detail::find_last( text, seek, std::equal_to() ); +} + +template< typename StringT, typename SubT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +string_constexpr typename StringT::const_iterator find_last( StringT const & text, SubT const & seek ) +{ + typedef typename StringT::value_type A; + + return detail::find_last( text, seek, std::equal_to() ); +} + +template< typename CharT, typename SubT > +string_constexpr CharT * find_last( CharT * text, SubT const & seek ) +{ + return detail::find_last( text, seek, std::equal_to() ); +} + +// contains(); C++23-like string::contains(): + +#if string_TEST_STRING_CONTAINS + +template< typename StringT, typename SubT + string_ENABLE_IF_HAS_METHOD_(StringT, contains) +> +string_nodiscard string_constexpr bool contains( StringT const & text, SubT const & seek ) string_noexcept +{ + return text.contains( seek ); +} + +template< typename StringT, typename SubT + string_DISABLE_IF_HAS_METHOD_(StringT, contains) + string_ENABLE_IF_( !std::is_arithmetic::value ) +> +string_nodiscard string_constexpr bool contains( StringT const & text, SubT const & seek ) string_noexcept +{ + return string::end( text ) != find_first( text, seek ); +} + +template< typename StringT, typename CharT + string_DISABLE_IF_HAS_METHOD_(StringT, contains) + string_ENABLE_IF_( std::is_arithmetic::value ) +> +string_nodiscard string_constexpr14 bool contains( StringT const & text, CharT seek ) string_noexcept +{ + CharT look[] = { seek, nullchr() }; + return string::end( text ) != find_first( text, look ); +} + +#else // string_TEST_STRING_CONTAINS + +template< typename StringT, typename SubT > +string_nodiscard string_constexpr bool contains( StringT const & text, SubT const & seek ) string_noexcept +{ + return string::cend( text ) != find_first( text, seek ); +} + +template< typename StringT > +string_nodiscard string_constexpr bool contains( StringT const & text, char const * seek ) string_noexcept +{ + return string::cend( text ) != find_first( text, seek ); +} + +template< typename StringT > +string_nodiscard string_constexpr bool contains( StringT const & text, char seek ) string_noexcept +{ + char look[] = { seek, nullchr() }; + return string::cend( text ) != find_first( text, look ); +} + +#endif // string_TEST_STRING_CONTAINS + +#if string_HAVE_REGEX + +template< typename StringT > +string_nodiscard string_constexpr bool contains( StringT const & text, std::regex const & re ) string_noexcept +{ + return std::regex_search( text, re ); +} + +template< typename StringT, typename ReT > +string_nodiscard string_constexpr bool contains_re( StringT const & text, ReT const & re ) string_noexcept +{ + return contains( text, std::regex(re) ); +} + +#endif // string_HAVE_REGEX + +// starts_with(): + +#if string_TEST_STRING_STARTS_WITH + +template< typename StringT, typename SubT + string_ENABLE_IF_HAS_METHOD_(StringT, starts_with) +> +string_nodiscard string_constexpr bool starts_with( StringT const & text, SubT const & seek ) string_noexcept +{ + return text.starts_with( seek ); +} + +template< typename StringT, typename SubT + string_DISABLE_IF_HAS_METHOD_(StringT, starts_with) + string_ENABLE_IF_( !std::is_arithmetic::value ) +> +string_nodiscard string_constexpr bool starts_with( StringT const & text, SubT const & seek ) string_noexcept +{ + typedef typename StringT::value_type A; + + return detail::starts_with( text, seek, std::equal_to() ); +} + +template< typename StringT, typename CharT + string_DISABLE_IF_HAS_METHOD_(StringT, starts_with) + string_ENABLE_IF_( std::is_arithmetic::value ) +> +string_nodiscard string_constexpr14 bool starts_with( StringT const & text, CharT seek ) string_noexcept +{ + CharT look[] = { seek, nullchr() }; + + // return detail::starts_with( text, look, std::equal_to() ); + return detail::starts_with( text, StringT(look), std::equal_to() ); +} + +#else // string_TEST_STRING_STARTS_WITH + +template< typename StringT, typename SubT > +string_nodiscard string_constexpr bool starts_with( StringT const & text, SubT const & seek ) string_noexcept +{ + typedef typename StringT::value_type A; + + return detail::starts_with( text, seek, std::equal_to() ); +} + +template< typename StringT > +string_nodiscard string_constexpr bool starts_with( StringT const & text, char const * seek ) string_noexcept +{ + typedef typename StringT::value_type A; + + return detail::starts_with( text, seek, std::equal_to() ); +} + +template< typename StringT > +string_nodiscard string_constexpr14 bool starts_with( StringT const & text, char seek ) string_noexcept +{ + char look[] = { seek, nullchr() }; + + return detail::starts_with( text, StringT(look), std::equal_to() ); +} + +#endif // string_TEST_STRING_STARTS_WITH + +// ends_with(): + +#if string_TEST_STRING_ENDS_WITH + +template< typename StringT, typename SubT + string_ENABLE_IF_HAS_METHOD_(StringT, ends_with) +> +string_nodiscard string_constexpr bool ends_with( StringT const & text, SubT const & seek ) string_noexcept +{ + return text.ends_with( seek ); +} + +template< typename StringT, typename SubT + string_DISABLE_IF_HAS_METHOD_(StringT, ends_with) + string_ENABLE_IF_( !std::is_arithmetic::value ) +> +string_nodiscard string_constexpr bool ends_with( StringT const & text, SubT const & seek ) string_noexcept +{ + typedef typename StringT::value_type A; + + return detail::ends_with( text, StringT(seek), std::equal_to() ); +} + +template< typename StringT, typename CharT + string_DISABLE_IF_HAS_METHOD_(StringT, ends_with) + string_ENABLE_IF_( std::is_arithmetic::value ) +> +string_nodiscard string_constexpr14 bool ends_with( StringT const & text, CharT seek ) string_noexcept +{ + CharT look[] = { seek, nullchr() }; + + return detail::ends_with( text, StringT(look), std::equal_to() ); +} + +#else // string_TEST_STRING_ENDS_WITH + +template< typename StringT, typename SubT > +string_nodiscard string_constexpr bool ends_with( StringT const & text, SubT const & seek ) string_noexcept +{ + typedef typename StringT::value_type A; + + return detail::ends_with( text, seek, std::equal_to() ); +} + +template< typename StringT > +string_nodiscard string_constexpr bool ends_with( StringT const & text, char const * seek ) string_noexcept +{ + typedef typename StringT::value_type A; + + return detail::ends_with( text, StringT(seek), std::equal_to() ); +} + +template< typename StringT > +string_nodiscard string_constexpr bool ends_with( StringT const & text, char seek ) string_noexcept +{ + char look[] = { seek, nullchr() }; + + return detail::ends_with( text, StringT(look), std::equal_to() ); +} + +#endif // string_TEST_STRING_ENDS_WITH + +// Modifiers: + +// replace_all(): + +template< typename CharT, typename FromT, typename ToT + // string_ENABLE_IF_HAS_METHOD_(StringT, replace) +> +string_nodiscard string_constexpr std::basic_string & +replace_all( std::basic_string & text, FromT const & from, ToT const & to ) string_noexcept +{ + return detail::replace_all( text, from, to ); +} + +template< typename StringT, typename FromT, typename ToT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +string_nodiscard string_constexpr StringT & +replace_all( StringT & text, FromT const & from, ToT const & to ) string_noexcept +{ + return detail::replace_all( text, from, to ); +} + +// replaced_all(): + +template< typename CharT, typename FromT, typename ToT > +string_nodiscard string_constexpr std::basic_string +replaced_all( CharT const * text, FromT const & from, ToT const & to ) string_noexcept +{ + std::basic_string result( text ); + + return replace_all( result, from, to ); +} + +template< typename StringT, typename FromT, typename ToT > +string_nodiscard string_constexpr StringT +replaced_all( StringT const & text, FromT const & from, ToT const & to ) string_noexcept +{ + StringT result( text ); + + return replace_all( result, from, to ); +} + +// replace_first(): + +template< typename CharT, typename FromT, typename ToT > +string_nodiscard string_constexpr CharT * +replace_first( CharT * text, FromT const & from, ToT const & to ) string_noexcept +{ + return detail::replace_first( text, from, to ); +} + +template< typename CharT, typename FromT, typename ToT > +string_nodiscard string_constexpr std::basic_string & +replace_first( std::basic_string & text, FromT const & from, ToT const & to ) string_noexcept +{ + return detail::replace_first( text, from, to ); +} + +template< typename StringT, typename FromT, typename ToT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +string_nodiscard string_constexpr StringT & +replace_first( StringT & text, FromT const & from, ToT const & to ) string_noexcept +{ + return detail::replace_first( text, from, to ); +} + +// replaced_first(): + +template< typename CharT, typename FromT, typename ToT > +string_nodiscard string_constexpr std::basic_string +replaced_first( CharT const * text, FromT const & from, ToT const & to ) string_noexcept +{ + std::basic_string result( text ); + + return replace_first( result, from, to ); +} + +template< typename StringT, typename FromT, typename ToT > +string_nodiscard string_constexpr StringT +replaced_first( StringT const & text, FromT const & from, ToT const & to ) string_noexcept +{ + StringT result( text ); + + return replace_first( result, from, to ); +} + +// replace_last(): + +template< typename CharT, typename FromT, typename ToT > +string_nodiscard string_constexpr CharT * +replace_last( CharT * text, FromT const & from, ToT const & to ) string_noexcept +{ + return detail::replace_last( text, from, to ); +} + +template< typename CharT, typename FromT, typename ToT > +string_nodiscard string_constexpr std::basic_string & +replace_last( std::basic_string & text, FromT const & from, ToT const & to ) string_noexcept +{ + return detail::replace_last( text, from, to ); +} + +template< typename StringT, typename FromT, typename ToT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +string_nodiscard string_constexpr StringT & +replace_last( StringT & text, FromT const & from, ToT const & to ) string_noexcept +{ + return detail::replace_last( text, from, to ); +} + +// replaced_last(): + +template< typename CharT, typename FromT, typename ToT > +string_nodiscard string_constexpr std::basic_string +replaced_last( CharT const * text, FromT const & from, ToT const & to ) string_noexcept +{ + std::basic_string result( text ); + + return replace_last( result, from, to ); +} + +template< typename StringT, typename FromT, typename ToT > +string_nodiscard string_constexpr StringT +replaced_last( StringT const & text, FromT const & from, ToT const & to ) string_noexcept +{ + StringT result( text ); + + return replace_last( result, from, to ); +} + +// clear(): + +template< typename CharT > +CharT * clear( CharT * cp ) string_noexcept +{ + *cp = nullchr(); + return cp; +} + +template< typename StringT + string_ENABLE_IF_HAS_METHOD_(StringT, clear) +> +StringT & clear( StringT & text ) string_noexcept +{ + text.clear(); + return text; +} + +// to_lowercase(), to_uppercase(): + +template< typename CharT > +CharT * to_lowercase( CharT * cp ) string_noexcept +{ + return detail::to_case( cp, detail::as_lowercase ); +} + +template< typename CharT > +CharT * to_uppercase( CharT * cp ) string_noexcept +{ + return detail::to_case( cp, detail::as_uppercase ); +} + +template< typename StringT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +StringT & to_lowercase( StringT & text ) string_noexcept +{ + return detail::to_case( text, detail::as_lowercase ); +} + +template< typename StringT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +StringT & to_uppercase( StringT & text ) string_noexcept +{ + return detail::to_case( text, detail::as_uppercase ); +} + +template< typename StringT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +string_nodiscard StringT as_lowercase( StringT const & text ) string_noexcept +{ + StringT result( text ); + to_lowercase( result ); + return result; +} + +template< typename StringT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +string_nodiscard StringT as_uppercase( StringT const & text ) string_noexcept +{ + StringT result( text ); + to_uppercase( result ); + return result; +} + +// append(): + +template< typename CharT, typename TailT > +string_constexpr CharT * +append( CharT * text, TailT const & tail ) string_noexcept +{ + return detail::append( text, tail ); +} + +template< typename StringT, typename TailT > +string_constexpr StringT & +append( StringT & text, TailT const & tail ) string_noexcept +{ + return detail::append( text, tail ); +} + +// appended(): + +template< typename StringT, typename TailT + string_ENABLE_IF_HAS_METHOD_(StringT, begin) +> +string_nodiscard string_constexpr StringT +appended( StringT const & text, TailT const & tail ) string_noexcept +{ + StringT result( text ); + + return detail::append( result, tail ); +} + +// TODO trim() + +// trim_left(): + +template< typename StringT > +inline StringT const default_trim_set() +{ + return " \t\n"; +} + +template< typename CharT, typename SetT > +string_constexpr CharT * +trim_left( CharT * text, SetT const * set ) string_noexcept +{ + return detail::trim_left( text, set ); +} + +template< typename CharT > +string_constexpr CharT * +trim_left( CharT * text ) string_noexcept +{ + return trim_left( text, default_trim_set() ); +} + +template< typename StringT, typename SetT > +string_constexpr StringT & +trim_left( StringT & text, SetT const & set ) string_noexcept +{ + return detail::trim_left( text, set ); +} + +template< typename StringT > +string_constexpr StringT & +trim_left( StringT & text ) string_noexcept +{ + return trim_left( text, default_trim_set() ); +} + +template< typename StringT, typename SetT > +string_constexpr14 StringT +trimmed_left( StringT const & text, SetT const & set ) string_noexcept +{ + StringT result( text ); + return detail::trim_left( result, set ); +} + +template< typename StringT > +string_constexpr StringT +trimmed_left( StringT const & text ) string_noexcept +{ + return trimmed_left( text, default_trim_set() ); +} + +// trim_right(): + +template< typename CharT, typename SetT > +string_constexpr CharT * +trim_right( CharT * text, SetT const * set ) string_noexcept +{ + return detail::trim_right( text, set ); +} + +template< typename CharT > +string_constexpr CharT * +trim_right( CharT * text ) string_noexcept +{ + return trim_right( text, default_trim_set() ); +} + +template< typename StringT, typename SetT > +string_constexpr StringT & +trim_right( StringT & text, SetT const & set ) string_noexcept +{ + return detail::trim_right( text, set ); +} + +template< typename StringT > +string_constexpr StringT & +trim_right( StringT & text ) string_noexcept +{ + return trim_right( text, default_trim_set() ); +} + +template< typename StringT, typename SetT > +string_constexpr14 StringT +trimmed_right( StringT const & text, SetT const & set ) string_noexcept +{ + StringT result( text ); + return detail::trim_right( result, set ); +} + +template< typename StringT > +string_constexpr StringT +trimmed_right( StringT const & text ) string_noexcept +{ + return trimmed_right( text, default_trim_set() ); +} + +// trim(): + +template< typename CharT, typename SetT > +string_constexpr CharT * +trim( CharT * text, SetT const * set ) string_noexcept +{ + return detail::trim( text, set ); +} + +template< typename CharT > +string_constexpr CharT * +trim( CharT * text ) string_noexcept +{ + return trim( text, default_trim_set() ); +} + +template< typename StringT, typename SetT > +string_constexpr StringT & +trim( StringT & text, SetT const & set ) string_noexcept +{ + return detail::trim( text, set ); +} + +template< typename StringT > +string_constexpr StringT & +trim( StringT & text ) string_noexcept +{ + return trim( text, default_trim_set() ); +} + +template< typename StringT, typename SetT > +string_constexpr StringT +trimmed( StringT const & text, SetT const & set ) string_noexcept +{ + StringT result( text ); + return detail::trim( result, set ); +} + +template< typename StringT > +string_constexpr StringT +trimmed( StringT const & text ) string_noexcept +{ + return trimmed( text, default_trim_set() ); +} + +// TODO join(): + +// Note: add way to defined return type: + +template< typename Coll, typename SepT +// string_ENABLE_IF_( !std::is_pointer::value ) +> +string_nodiscard string_constexpr14 +typename Coll::value_type +join( Coll const & coll, SepT const & sep ) string_noexcept +{ + typename Coll::value_type result; + + typename Coll::const_iterator const coll_begin = string::cbegin(coll); + typename Coll::const_iterator const coll_end = string::cend(coll); + + typename Coll::const_iterator pos = coll_begin; + + if ( pos != coll_end ) + { + append( result, *pos++ ); + } + + for ( ; pos != coll_end; ++pos ) + { + append( append( result, sep ), *pos ); + } + + return result; +} + +// split(): + +// Various kinds of delimiters: +// - literal_delimiter - a single string delimiter +// - any_of_delimiter - any of given characters as delimiter +// - fixed_delimiter - fixed length delimiter +// - limit_delimiter - not implemented +// - regex_delimiter - regular expression delimiter +// - char_delimiter - single-char delimiter + +template< typename CharT > +basic_string_view basic_delimiter_end(basic_string_view sv) +{ +#if string_CONFIG_SELECT_STRING_VIEW != string_CONFIG_SELECT_STRING_VIEW_STD + return basic_string_view(sv.cend(), size_t(0)); +#else + return basic_string_view(sv.data() + sv.size(), size_t(0)); +#endif +} + +// a single string delimiter: + +template< typename CharT > +class basic_literal_delimiter +{ + const std::basic_string delimiter_; + mutable size_t found_; + +public: + explicit basic_literal_delimiter(basic_string_view sv) + : delimiter_(to_string(sv)) + , found_(0) + {} + + size_t length() const + { + return delimiter_.length(); + } + + basic_string_view operator()(basic_string_view text, size_t pos) const + { + return find(text, pos); + } + + basic_string_view find(basic_string_view text, size_t pos) const + { + // out of range, return 'empty' if last match was at end of text, else return 'done': + if ( pos >= text.length()) + { + // last delimiter match at end of text? + if ( found_ != text.length() - 1 ) + return basic_delimiter_end(text); + + found_ = 0; + return text.substr(text.length() - 1, 0); + } + + // a single character at a time: + if (0 == delimiter_.length()) + { + return text.substr(pos, 1); + } + + found_ = text.find(delimiter_, pos); + + // at a delimiter, or searching past the last delimiter: + if (found_ == pos || pos == text.length()) + { + return text.substr(pos, 0); + } + + // no delimiter found: + if (found_ == basic_string_view::npos) + { + // return remaining text: + if (pos < text.length()) + { + return text.substr(pos); + } + + // nothing left, return 'done': + return basic_delimiter_end(text); + } + + // delimited text: + return text.substr(pos, found_ - pos); + } +}; + +// any of given characters as delimiter: + +template< typename CharT > +class basic_any_of_delimiter +{ + const std::basic_string delimiters_; + +public: + explicit basic_any_of_delimiter(basic_string_view sv) + : delimiters_(to_string(sv)) {} + + size_t length() const + { + return (min)( size_t(1), delimiters_.length()); + } + + basic_string_view operator()(basic_string_view text, size_t pos) const + { + return find(text, pos); + } + + basic_string_view find(basic_string_view text, size_t pos) const + { + // out of range, return 'done': + if ( pos > text.length()) + return basic_delimiter_end(text); + + // a single character at a time: + if (0 == delimiters_.length()) + { + return text.substr(pos, 1); + } + + size_t found = text.find_first_of(delimiters_, pos); + + // at a delimiter, or searching past the last delimiter: + if (found == pos || (pos == text.length())) + { + return basic_string_view(); + } + + // no delimiter found: + if (found == basic_string_view::npos) + { + // return remaining text: + if (pos < text.length()) + { + return text.substr(pos); + } + + // nothing left, return 'done': + return basic_delimiter_end(text); + } + + // delimited text: + return text.substr(pos, found - pos); + } +}; + +// fixed length delimiter: + +template< typename CharT > +class basic_fixed_delimiter +{ + size_t len_; + +public: + explicit basic_fixed_delimiter(size_t len) + : len_(len) {} + + size_t length() const + { + return 0; + } + + string_view operator()(basic_string_view text, size_t pos) const + { + return find(text, pos); + } + + string_view find(basic_string_view text, size_t pos) const + { + // out of range, return 'done': + if ( pos > text.length()) + return basic_delimiter_end(text); + + // current slice: + return text.substr(pos, len_); + } +}; + +// TODO limit_delimiter - Delimiter template would take another Delimiter and a size_t limiting +// the given delimiter to matching a max numbers of times. This is similar to the 3rd argument to +// perl's split() function. + +template< typename CharT, typename DelimiterT > +class basic_limit_delimiter; + +// regular expression delimiter: + +#if string_HAVE_REGEX + +template< typename CharT > +class basic_regex_delimiter +{ + std::regex delimiter_re_; // regular expression designating delimiters + size_t delimiter_len_; // length of regular expression + mutable size_t matched_delimiter_length_; // length of the actually matched delimiter + mutable bool trailing_delimiter_seen; // whether to provide last empty result + +public: + explicit basic_regex_delimiter(basic_string_view sv) + : delimiter_re_(to_string(sv)) + , delimiter_len_(sv.length()) + , matched_delimiter_length_(0u) + , trailing_delimiter_seen(false) + {} + + size_t length() const + { + return matched_delimiter_length_; + } + + basic_string_view operator()(basic_string_view text, size_t pos) const + { + return find(text, pos); + } + + basic_string_view find(basic_string_view text, size_t pos) const + { + // trailing empty entry: + // TODO this feels like a hack, don't know any better at this moment + if (trailing_delimiter_seen) + { + trailing_delimiter_seen = false; + return basic_string_view(); + } + + // out of range, return 'done': + if ( pos > text.length()) + return basic_delimiter_end(text); + + // a single character at a time: + if (0 == delimiter_len_) + { + return text.substr(pos, 1); + } + + std::smatch m; + std::basic_string s = to_string(text.substr(pos)); + + const bool found = std::regex_search(s, m, delimiter_re_); + + matched_delimiter_length_ = m.length(); + + // no delimiter found: + if (!found) + { + // return remaining text: + if (pos < text.length()) + { + return text.substr(pos); + } + + // nothing left, return 'done': + return basic_delimiter_end(text); + } + + // at a trailing delimiter, remember for next round: + else if ((size_t(m.position()) == s.length() - 1)) + { + trailing_delimiter_seen = true; + } + + // delimited text, the match in the input string: + return text.substr(pos, m.position()); + } +}; + +#endif // string_HAVE_REGEX + +// single-char delimiter: + +template< typename CharT > +class basic_char_delimiter +{ + CharT c_; + +public: + explicit basic_char_delimiter(CharT c) + : c_(c) {} + + size_t length() const + { + return 1; + } + + basic_string_view operator()(basic_string_view text, size_t pos) const + { + return find(text, pos); + } + + basic_string_view find(basic_string_view text, size_t pos) const + { + size_t found = text.find(c_, pos); + + // nothing left, return 'done': + if (found == basic_string_view::npos) + return basic_delimiter_end(text); + + // the c_ in the input string: + return text.substr(found, 1); + } +}; + + // typedefs: + +typedef basic_literal_delimiter< char > literal_delimiter; +typedef basic_literal_delimiter wliteral_delimiter; +typedef basic_any_of_delimiter< char > any_of_delimiter; +typedef basic_any_of_delimiter< wchar_t> wany_of_delimiter; +typedef basic_fixed_delimiter< char > fixed_delimiter; +typedef basic_fixed_delimiter< wchar_t> wfixed_delimiter; +typedef basic_char_delimiter< char > char_delimiter; +typedef basic_char_delimiter< wchar_t> wchar_t_delimiter; +// typedef basic_limit_delimiter< char > limit_delimiter; +// typedef basic_limit_delimiter< wchar_t> wlimit_delimiter; + +#if string_HAVE_REGEX +typedef basic_regex_delimiter< char > regex_delimiter; +typedef basic_regex_delimiter< wchar_t> wregex_delimiter; +#endif + +#if string_HAVE_CHAR16_T + +typedef basic_literal_delimiter u16literal_delimiter; +typedef basic_literal_delimiter u32literal_delimiter; +typedef basic_any_of_delimiter< char16_t> u16any_of_delimiter; +typedef basic_any_of_delimiter< char32_t> u32any_of_delimiter; +typedef basic_fixed_delimiter< char16_t> u16fixed_delimiter; +typedef basic_fixed_delimiter< char32_t> u32fixed_delimiter; +typedef basic_char_delimiter< char16_t> u16char_delimiter; +typedef basic_char_delimiter< char32_t> u32char_delimiter; +// typedef basic_limit_delimiter< char16_t> u16limit_delimiter; +// typedef basic_limit_delimiter< char32_t> u32limit_delimiter; + +#if string_HAVE_REGEX +typedef basic_regex_delimiter< char16_t> u16regex_delimiter; +typedef basic_regex_delimiter< char32_t> u32regex_delimiter; +#endif + +#endif // string_HAVE_CHAR16_T + +// split(): + +template std::vector< string_view> split( string_view text, Delimiter delimiter) { return detail::split(text, delimiter); } +template std::vector split(wstring_view text, Delimiter delimiter) { return detail::split(text, delimiter); } + +#if string_HAVE_CHAR16_T +template std::vector split(u16string_view text, Delimiter delimiter) { return detail::split(text, delimiter); } +template std::vector split(u32string_view text, Delimiter delimiter) { return detail::split(text, delimiter); } +#endif + +inline std::vector split(string_view text, char const * d) { return detail::split(text, literal_delimiter(d)); } + +} // namespace string +} // namespace nonstd + +namespace nonstd { + +// using string::clear; + +} // namespace nonstd + +#endif // NONSTD_STRING_LITE_HPP diff --git a/src/str.cpp b/src/str.cpp index af645d78..0dbb0d2b 100644 --- a/src/str.cpp +++ b/src/str.cpp @@ -14,9 +14,13 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "str.hpp" + +#include #include #include #include +#include #include #include @@ -104,6 +108,28 @@ namespace str } } + void + splitkv(const std::string_view &str_, + const char delimiter_, + std::string &key_, + std::string_view &val_) + { + size_t pos; + + pos = str_.find(delimiter_); + if(pos != std::string_view::npos) + { + key_ = str_.substr(0,pos); + val_ = str_.substr(pos + 1, + str_.size() - pos + 1); + } + else + { + key_ = str_; + val_ = std::string_view{}; + } + } + void splitkv(const string &str_, const char delimiter_, @@ -272,3 +298,26 @@ namespace str return rv; } } + +bool +str::eq(const char *s0_, + const char *s1_) +{ + return (strcmp(s0_,s1_) == 0); +} + +bool +str::startswith(const char *s_, + const char *p_) +{ + while(*p_) + { + if(*p_ != *s_) + return false; + + p_++; + s_++; + } + + return true; +} diff --git a/src/str.hpp b/src/str.hpp index 39e17eb2..c8abcf46 100644 --- a/src/str.hpp +++ b/src/str.hpp @@ -56,6 +56,12 @@ namespace str std::string *key, std::string *value); + void + splitkv(const std::string_view &str, + const char delimiter, + std::string &key, + std::string_view &val); + std::string join(const std::vector &vec, const size_t substridx, @@ -91,10 +97,18 @@ namespace str startswith(const std::string &str_, const std::string &prefix_); + bool + startswith(const char *str, + const char *prefix); + bool endswith(const std::string &str_, const std::string &suffix_); std::string trim(const std::string &str); + + bool + eq(const char *s0, + const char *s1); }