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); }