diff --git a/README.md b/README.md index a124ac32..2ddad1d3 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,8 @@ mergerfs does **not** support the copy-on-write (CoW) behavior found in **aufs** * **security_capability=true|false**: If false return ENOATTR when xattr security.capability is queried. (default: true) * **xattr=passthrough|noattr|notsup**: Runtime control of xattrs. Default is to passthrough xattr requests. 'noattr' will short circuit as if nothing exists. 'notsup' will respond with ENOTSUP as if xattrs are not supported or disabled. (default: passthrough) * **link_cow=true|false**: When enabled if a regular file is opened which has a link count > 1 it will copy the file to a temporary file and rename over the original. Breaking the link and providing a basic copy-on-write function similar to cow-shell. (default: false) +* **statfs=base|full**: Controls how statfs works. 'base' means it will always use all branches in statfs calculations. 'full' is in effect path preserving and only includes drives where the path exists. (default: base) +* **statfs_ignore=none|ro|nc**: 'ro' will cause statfs calculations to ignore available space for branches mounted or tagged as 'read only' or 'no create'. 'nc' will ignore available space for branches tagged as 'no create'. (default: none) * **threads=num**: number of threads to use in multithreaded mode. When set to zero (the default) it will attempt to discover and use the number of logical cores. If the lookup fails it will fall back to using 4. If the thread count is set negative it will look up the number of cores then divide by the absolute value. ie. threads=-2 on an 8 core machine will result in 8 / 2 = 4 threads. There will always be at least 1 thread. NOTE: higher number of threads increases parallelism but usually decreases throughput. (default: number of cores) *NOTE2:* the option is unavailable when built with system libfuse. * **fsname=name**: sets the name of the filesystem as seen in **mount**, **df**, etc. Defaults to a list of the source paths concatenated together with the longest common prefix removed. * **func.<func>=<policy>**: sets the specific FUSE function's policy. See below for the list of value types. Example: **func.getattr=newest** diff --git a/man/mergerfs.1 b/man/mergerfs.1 index 580ddf96..388d9610 100644 --- a/man/mergerfs.1 +++ b/man/mergerfs.1 @@ -170,6 +170,20 @@ Breaking the link and providing a basic copy\-on\-write function similar to cow\-shell. (default: false) .IP \[bu] 2 +\f[B]statfs=base|full\f[]: Controls how statfs works. +\[aq]base\[aq] means it will always use all branches in statfs +calculations. +\[aq]full\[aq] is in effect path preserving and only includes drives +where the path exists. +(default: base) +.IP \[bu] 2 +\f[B]statfs_ignore=none|ro|nc\f[]: \[aq]ro\[aq] will cause statfs +calculations to ignore available space for branches mounted or tagged as +\[aq]read only\[aq] or \[aq]no create\[aq]. +\[aq]nc\[aq] will ignore available space for branches tagged as \[aq]no +create\[aq]. +(default: none) +.IP \[bu] 2 \f[B]threads=num\f[]: number of threads to use in multithreaded mode. When set to zero (the default) it will attempt to discover and use the number of logical cores. @@ -219,7 +233,7 @@ globbing (http://linux.die.net/man/7/glob). the shell itself will apply the glob itself.\f[] .PP Each branch can have a suffix of \f[C]=RW\f[] (read / write), -\f[C]=RO\f[] (read only), or \f[C]=NW\f[] (no writes). +\f[C]=RO\f[] (read only), or \f[C]=NC\f[] (no create). These suffixes work with globs as well and will apply to each path found. \f[C]RW\f[] is the default behavior and those paths will be eligible for @@ -227,7 +241,7 @@ all policy categories. \f[C]RO\f[] will exclude those paths from \f[C]create\f[] and \f[C]action\f[] policies (just as a filesystem being mounted \f[C]ro\f[] would). -\f[C]NW\f[] will exclude those paths from \f[C]create\f[] policies (you +\f[C]NC\f[] will exclude those paths from \f[C]create\f[] policies (you can\[aq]t create but you can change / delete). .IP .nf @@ -433,7 +447,8 @@ drives as necessary. .SS Policy descriptions .PP All \f[B]create\f[] policies will filter out branches which are mounted -\f[B]read only\f[] or tagged as \f[B]read only\f[] or \f[B]no write\f[]. +\f[B]read only\f[] or tagged as \f[B]read only\f[] or \f[B]no +create\f[]. All \f[B]action\f[] policies will filter out branches which are mounted or tagged as \f[B]read only\f[]. .PP diff --git a/src/branch.cpp b/src/branch.cpp index 5a3d0b13..149d7925 100644 --- a/src/branch.cpp +++ b/src/branch.cpp @@ -34,6 +34,12 @@ Branch::ro(void) const return (mode == Branch::RO); } +bool +Branch::nc(void) const +{ + return (mode == Branch::NC); +} + bool Branch::ro_or_nc(void) const { diff --git a/src/branch.hpp b/src/branch.hpp index 5a54ba87..3bdd97ae 100644 --- a/src/branch.hpp +++ b/src/branch.hpp @@ -35,6 +35,7 @@ struct Branch std::string path; bool ro(void) const; + bool nc(void) const; bool ro_or_nc(void) const; }; diff --git a/src/config.cpp b/src/config.cpp index 442837f2..ef5b4bbd 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -48,6 +48,8 @@ namespace mergerfs security_capability(true), link_cow(false), xattr(0), + statfs(StatFS::BASE), + statfs_ignore(StatFSIgnore::NONE), POLICYINIT(access), POLICYINIT(chmod), POLICYINIT(chown), diff --git a/src/config.hpp b/src/config.hpp index 673e512c..61aa12d8 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -32,6 +32,26 @@ namespace mergerfs { class Config { + public: + struct StatFS + { + enum Enum + { + BASE, + FULL + }; + }; + + struct StatFSIgnore + { + enum Enum + { + NONE, + RO, + NC + }; + }; + public: Config(); @@ -56,6 +76,8 @@ namespace mergerfs bool security_capability; bool link_cow; int xattr; + StatFS::Enum statfs; + StatFSIgnore::Enum statfs_ignore; public: const Policy *policies[FuseFunc::Enum::END]; diff --git a/src/fs_base_statvfs.hpp b/src/fs_base_statvfs.hpp index 2172437a..3b815810 100644 --- a/src/fs_base_statvfs.hpp +++ b/src/fs_base_statvfs.hpp @@ -19,6 +19,8 @@ #pragma once #include "errno.hpp" +#include "fs_base_close.hpp" +#include "fs_base_open.hpp" #include @@ -43,4 +45,33 @@ namespace fs { return fs::statvfs(path.c_str(),st); } + + static + inline + int + fstatvfs(const int fd_, + struct statvfs &st_) + { + return ::fstatvfs(fd_,&st_); + } + + static + inline + int + lstatvfs(const std::string &path_, + struct statvfs &st_) + { + int fd; + int rv; + + fd = fs::open(path_,O_RDONLY|O_NOFOLLOW|O_PATH); + if(fd == -1) + return -1; + + rv = fs::fstatvfs(fd,st_); + + fs::close(fd); + + return rv; + } } diff --git a/src/getxattr.cpp b/src/getxattr.cpp index 9da0a800..85ab6989 100644 --- a/src/getxattr.cpp +++ b/src/getxattr.cpp @@ -170,6 +170,47 @@ _getxattr_controlfile_errno(const int errno_, } } +static +void +_getxattr_controlfile_statfs(const Config::StatFS::Enum enum_, + string &attrvalue_) +{ + switch(enum_) + { + case Config::StatFS::BASE: + attrvalue_ = "base"; + break; + case Config::StatFS::FULL: + attrvalue_ = "full"; + break; + default: + attrvalue_ = "ERROR"; + break; + } +} + +static +void +_getxattr_controlfile_statfsignore(const Config::StatFSIgnore::Enum enum_, + string &attrvalue_) +{ + switch(enum_) + { + case Config::StatFSIgnore::NONE: + attrvalue_ = "none"; + break; + case Config::StatFSIgnore::RO: + attrvalue_ = "ro"; + break; + case Config::StatFSIgnore::NC: + attrvalue_ = "nc"; + break; + default: + attrvalue_ = "ERROR"; + break; + } +} + static void _getxattr_controlfile_policies(const Config &config, @@ -246,6 +287,10 @@ _getxattr_controlfile(const Config &config, _getxattr_controlfile_errno(config.xattr,attrvalue); else if(attr[2] == "link_cow") _getxattr_controlfile_bool(config.link_cow,attrvalue); + else if(attr[2] == "statfs") + _getxattr_controlfile_statfs(config.statfs,attrvalue); + else if(attr[2] == "statfs_ignore") + _getxattr_controlfile_statfsignore(config.statfs_ignore,attrvalue); else if(attr[2] == "policies") _getxattr_controlfile_policies(config,attrvalue); else if(attr[2] == "version") diff --git a/src/listxattr.cpp b/src/listxattr.cpp index f8fec8e0..ce2a64a7 100644 --- a/src/listxattr.cpp +++ b/src/listxattr.cpp @@ -55,6 +55,8 @@ _listxattr_controlfile(char *list, ("user.mergerfs.link_cow") ("user.mergerfs.security_capability") ("user.mergerfs.xattr") + ("user.mergerfs.statfs") + ("user.mergerfs.statfs_ignore") ("user.mergerfs.policies") ("user.mergerfs.version") ("user.mergerfs.pid"); diff --git a/src/option_parser.cpp b/src/option_parser.cpp index cfb24a26..120e9357 100644 --- a/src/option_parser.cpp +++ b/src/option_parser.cpp @@ -170,6 +170,38 @@ parse_and_process_errno(const std::string &value_, return 0; } +static +int +parse_and_process_statfs(const std::string &value_, + Config::StatFS::Enum &enum_) +{ + if(value_ == "base") + enum_ = Config::StatFS::BASE; + else if(value_ == "full") + enum_ = Config::StatFS::FULL; + else + return 1; + + return 0; +} + +static +int +parse_and_process_statfsignore(const std::string &value_, + Config::StatFSIgnore::Enum &enum_) +{ + if(value_ == "none") + enum_ = Config::StatFSIgnore::NONE; + else if(value_ == "ro") + enum_ = Config::StatFSIgnore::RO; + else if(value_ == "nc") + enum_ = Config::StatFSIgnore::NC; + else + return 1; + + return 0; +} + static int parse_and_process_arg(Config &config, @@ -190,9 +222,10 @@ parse_and_process_kv_arg(Config &config, const std::string &key, const std::string &value) { - int rv = -1; + int rv; std::vector keypart; + rv = -1; str::split(keypart,key,'.'); if(keypart.size() == 2) { @@ -223,6 +256,10 @@ parse_and_process_kv_arg(Config &config, rv = parse_and_process(value,config.link_cow); else if(key == "xattr") rv = parse_and_process_errno(value,config.xattr); + else if(key == "statfs") + rv = parse_and_process_statfs(value,config.statfs); + else if(key == "statfs_ignore") + rv = parse_and_process_statfsignore(value,config.statfs_ignore); } if(rv == -1) @@ -334,6 +371,16 @@ usage(void) " filesystems. notattr will short circuit as if\n" " nothing exists. notsup will respond as if not\n" " supported or disabled. default = passthrough\n" + " -o statfs=base|full When set to 'base' statfs will use all branches\n" + " when performing statfs calculations. 'full' will\n" + " only include branches on which that path is\n" + " available. default = base\n" + " -o statfs_ignore=none|ro|nc\n" + " 'ro' will cause statfs calculations to ignore\n" + " available space for branches mounted or tagged\n" + " as 'read only' or 'no create'. 'nc' will ignore\n" + " available space for branches tagged as\n" + " 'no create'. default = none\n" << std::endl; } diff --git a/src/setxattr.cpp b/src/setxattr.cpp index a4bd3864..03621847 100644 --- a/src/setxattr.cpp +++ b/src/setxattr.cpp @@ -156,6 +156,67 @@ _setxattr_bool(const string &attrval, return 0; } +static +int +_setxattr_xattr(const string &attrval_, + const int flags_, + int xattr_) +{ + if((flags_ & XATTR_CREATE) == XATTR_CREATE) + return -EEXIST; + + if(attrval_ == "passthrough") + xattr_ = 0; + else if(attrval_ == "noattr") + xattr_ = ENOATTR; + else if(attrval_ == "notsup") + xattr_ = ENOTSUP; + else + return -EINVAL; + + return 0; +} + +static +int +_setxattr_statfs(const string &attrval_, + const int flags_, + Config::StatFS::Enum &enum_) +{ + if((flags_ & XATTR_CREATE) == XATTR_CREATE) + return -EEXIST; + + if(attrval_ == "base") + enum_ = Config::StatFS::BASE; + else if(attrval_ == "full") + enum_ = Config::StatFS::FULL; + else + return -EINVAL; + + return 0; +} + +static +int +_setxattr_statfsignore(const string &attrval_, + const int flags_, + Config::StatFSIgnore::Enum &enum_) +{ + if((flags_ & XATTR_CREATE) == XATTR_CREATE) + return -EEXIST; + + if(attrval_ == "none") + enum_ = Config::StatFSIgnore::NONE; + else if(attrval_ == "ro") + enum_ = Config::StatFSIgnore::RO; + else if(attrval_ == "nc") + enum_ = Config::StatFSIgnore::NC; + else + return -EINVAL; + + return 0; +} + static int _setxattr_controlfile_func_policy(Config &config, @@ -246,10 +307,22 @@ _setxattr_controlfile(Config &config, return _setxattr_bool(attrval, flags, config.security_capability); + else if(attr[2] == "xattr") + return _setxattr_xattr(attrval, + flags, + config.xattr); else if(attr[2] == "link_cow") return _setxattr_bool(attrval, flags, config.link_cow); + else if(attr[2] == "statfs") + return _setxattr_statfs(attrval, + flags, + config.statfs); + else if(attr[2] == "statfs_ignore") + return _setxattr_statfsignore(attrval, + flags, + config.statfs_ignore); break; case 4: diff --git a/src/statfs.cpp b/src/statfs.cpp index f9d2fd45..7a68ef19 100644 --- a/src/statfs.cpp +++ b/src/statfs.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2016, Antonio SJ Musumeci + Copyright (c) 2018, 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 @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include #include @@ -26,12 +26,17 @@ #include "errno.hpp" #include "fs_base_stat.hpp" #include "fs_base_statvfs.hpp" +#include "fs_path.hpp" #include "rwlock.hpp" +#include "statvfs_util.hpp" #include "ugid.hpp" using std::string; using std::map; using std::vector; +using mergerfs::Config; +typedef Config::StatFS StatFS; +typedef Config::StatFSIgnore StatFSIgnore; static void @@ -41,7 +46,7 @@ _normalize_statvfs(struct statvfs *fsstat, const unsigned long min_namemax) { fsstat->f_blocks = (fsblkcnt_t)((fsstat->f_blocks * fsstat->f_frsize) / min_frsize); - fsstat->f_bfree = (fsblkcnt_t)((fsstat->f_bfree * fsstat->f_frsize) / min_frsize); + fsstat->f_bfree = (fsblkcnt_t)((fsstat->f_bfree * fsstat->f_frsize) / min_frsize); fsstat->f_bavail = (fsblkcnt_t)((fsstat->f_bavail * fsstat->f_frsize) / min_frsize); fsstat->f_bsize = min_bsize; fsstat->f_frsize = min_frsize; @@ -63,48 +68,64 @@ _merge_statvfs(struct statvfs * const out, } static -void -_statfs_core(const string &path_, - unsigned long &min_bsize, - unsigned long &min_frsize, - unsigned long &min_namemax, - map &fsstats) +bool +_should_ignore(const StatFSIgnore::Enum ignore_, + const Branch *branch_, + const bool readonly_) { - int rv; - struct stat st; - struct statvfs fsstat; - - rv = fs::statvfs(path_,fsstat); - if(rv == -1) - return; - - rv = fs::stat(path_,st); - if(rv == -1) - return; - - if(fsstat.f_bsize && (min_bsize > fsstat.f_bsize)) - min_bsize = fsstat.f_bsize; - if(fsstat.f_frsize && (min_frsize > fsstat.f_frsize)) - min_frsize = fsstat.f_frsize; - if(fsstat.f_namemax && (min_namemax > fsstat.f_namemax)) - min_namemax = fsstat.f_namemax; - - fsstats.insert(std::make_pair(st.st_dev,fsstat)); + return ((((ignore_ == StatFSIgnore::RO) || readonly_) && + (branch_->ro_or_nc())) || + ((ignore_ == StatFSIgnore::NC) && (branch_->nc()))); } static int -_statfs(const Branches &branches_, - struct statvfs &fsstat) +_statfs(const Branches &branches_, + const char *fusepath, + const StatFS::Enum mode, + const StatFSIgnore::Enum ignore, + struct statvfs &fsstat) { + int rv; + string fullpath; + struct stat st; + struct statvfs stvfs; + unsigned long min_bsize; + unsigned long min_frsize; + unsigned long min_namemax; map fsstats; - unsigned long min_bsize = ULONG_MAX; - unsigned long min_frsize = ULONG_MAX; - unsigned long min_namemax = ULONG_MAX; + min_bsize = std::numeric_limits::max(); + min_frsize = std::numeric_limits::max(); + min_namemax = std::numeric_limits::max(); for(size_t i = 0, ei = branches_.size(); i < ei; i++) { - _statfs_core(branches_[i].path,min_bsize,min_frsize,min_namemax,fsstats); + fullpath = ((mode == StatFS::FULL) ? + fs::path::make(&branches_[i].path,fusepath) : + branches_[i].path); + + rv = fs::lstat(fullpath,st); + if(rv == -1) + continue; + + rv = fs::lstatvfs(fullpath,stvfs); + if(rv == -1) + continue; + + if(stvfs.f_bsize && (min_bsize > stvfs.f_bsize)) + min_bsize = stvfs.f_bsize; + if(stvfs.f_frsize && (min_frsize > stvfs.f_frsize)) + min_frsize = stvfs.f_frsize; + if(stvfs.f_namemax && (min_namemax > stvfs.f_namemax)) + min_namemax = stvfs.f_namemax; + + if(_should_ignore(ignore,&branches_[i],StatVFS::readonly(stvfs))) + { + stvfs.f_bavail = 0; + stvfs.f_favail = 0; + } + + fsstats.insert(std::make_pair(st.st_dev,stvfs)); } map::iterator iter = fsstats.begin(); @@ -138,6 +159,9 @@ namespace mergerfs const rwlock::ReadGuard readlock(&config.branches_lock); return _statfs(config.branches, + fusepath, + config.statfs, + config.statfs_ignore, *stat); } }