diff --git a/README.md b/README.md index 3cf1252a..86ef1f9e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ % mergerfs(1) mergerfs user manual % Antonio SJ Musumeci -% 2018-10-05 +% 2018-10-08 # NAME @@ -72,6 +72,7 @@ mergerfs does **not** support the copy-on-write (CoW) behavior found in **aufs** * **nullrw=true|false**: turns reads and writes into no-ops. The request will succeed but do nothing. Useful for benchmarking mergerfs. (default: false) * **ignorepponrename=true|false**: ignore path preserving on rename. Typically rename and link act differently depending on the policy of `create` (read below). Enabling this will cause rename and link to always use the non-path preserving behavior. This means files, when renamed or linked, will stay on the same drive. (default: false) * **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) * **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. @@ -142,6 +143,14 @@ $ dd if=/path/to/mergerfs/mount/benchmark of=/dev/null bs=1M count=1024 It's important to test with different `obs` (output block size) values since the relative overhead is greater with smaller values. As you can see above the size of a read or write can massively impact theoretical performance. If an application performs much worse through mergerfs it could very well be that it doesn't optimally size its read and write requests. +### xattr + +Runtime extended attribute support can be managed via the `xattr` option. By default it will passthrough any xattr calls. Given xattr support is rarely used and can have significant performance implications mergerfs allows it to be disabled at runtime. + +`noattr` will cause mergerfs to short circuit all xattr calls and return ENOATTR where appropriate. mergerfs still gets all the requests but they will not be forwarded on to the underlying filesystems. The runtime control will still function in this mode. + +`notsup` will cause mergerfs to return ENOTSUP for any xattr call. The difference with `noattr` is that the kernel will cache this fact and itself short circuit future calls. This will be more efficient than `noattr` but will cause mergerfs' runtime control via the hidden file to stop working. + # FUNCTIONS / POLICIES / CATEGORIES The POSIX filesystem API has a number of functions. **creat**, **stat**, **chown**, etc. In mergerfs these functions are grouped into 3 categories: **action**, **create**, and **search**. Functions and categories can be assigned a policy which dictates how **mergerfs** behaves. Any policy can be assigned to a function or category though some may not be very useful in practice. For instance: **rand** (random) may be useful for file creation (create) but could lead to very odd behavior if used for `chmod` (though only if there were more than one copy of the file). @@ -311,8 +320,6 @@ $ sudo make INTERNAL_FUSE=0 install 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. -Even if xattrs are disabled for mergerfs the [{list,get,set}xattrs](http://linux.die.net/man/2/listxattr) calls against this pseudo file will still work. - 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 ##### @@ -414,7 +421,7 @@ A B C * mergerfs.balance: Rebalance files across drives by moving them from the most filled to the least filled * mergerfs.mktrash: Creates FreeDesktop.org Trash specification compatible directories on a mergerfs mount * https://github.com/trapexit/scorch - * scorch: A tool to help discover silent corruption of files + * scorch: A tool to help discover silent corruption of files and keep track of files * https://github.com/trapexit/bbf * bbf (bad block finder): a tool to scan for and 'fix' hard drive bad blocks and find the files using those blocks @@ -746,6 +753,8 @@ and the kernel use internally (also called the "nodeid"). Due to how NFS works and interacts with FUSE when not using `direct_io` its possible that a getxattr for `security.capability` will be issued prior to any write. This will usually result in a massive slowdown for writes. Using `direct_io` will keep this from happening (and generally good to enable unless you need the features it disables) but the `security_capability` option can also help by short circuiting the call and returning `ENOATTR`. +You could also set `xattr` to `noattr` or `notsup` to short circuit or stop all xattr requests. + #### It's mentioned that there are some security issues with mhddfs. What are they? How does mergerfs address them? [mhddfs](https://github.com/trapexit/mhddfs) manages running as **root** by calling [getuid()](https://github.com/trapexit/mhddfs/blob/cae96e6251dd91e2bdc24800b4a18a74044f6672/src/main.c#L319) and if it returns **0** then it will [chown](http://linux.die.net/man/1/chown) the file. Not only is that a race condition but it doesn't handle many other situations. Rather than attempting to simulate POSIX ACL behavior the proper way to manage this is to use [seteuid](http://linux.die.net/man/2/seteuid) and [setegid](http://linux.die.net/man/2/setegid), in effect becoming the user making the original call, and perform the action as them. This is what mergerfs does. diff --git a/man/mergerfs.1 b/man/mergerfs.1 index 1c329039..27a13511 100644 --- a/man/mergerfs.1 +++ b/man/mergerfs.1 @@ -1,7 +1,7 @@ .\"t .\" Automatically generated by Pandoc 1.19.2.4 .\" -.TH "mergerfs" "1" "2018\-10\-05" "mergerfs user manual" "" +.TH "mergerfs" "1" "2018\-10\-08" "mergerfs user manual" "" .hy .SH NAME .PP @@ -156,6 +156,13 @@ This means files, when renamed or linked, will stay on the same drive. xattr security.capability is queried. (default: true) .IP \[bu] 2 +\f[B]xattr=passthrough|noattr|notsup\f[]: Runtime control of xattrs. +Default is to passthrough xattr requests. +\[aq]noattr\[aq] will short circuit as if nothing exists. +\[aq]notsup\[aq] will respond with ENOTSUP as if xattrs are not +supported or disabled. +(default: passthrough) +.IP \[bu] 2 \f[B]link_cow=true|false\f[]: 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. @@ -309,6 +316,26 @@ As you can see above the size of a read or write can massively impact theoretical performance. If an application performs much worse through mergerfs it could very well be that it doesn\[aq]t optimally size its read and write requests. +.SS xattr +.PP +Runtime extended attribute support can be managed via the \f[C]xattr\f[] +option. +By default it will passthrough any xattr calls. +Given xattr support is rarely used and can have significant performance +implications mergerfs allows it to be disabled at runtime. +.PP +\f[C]noattr\f[] will cause mergerfs to short circuit all xattr calls and +return ENOATTR where appropriate. +mergerfs still gets all the requests but they will not be forwarded on +to the underlying filesystems. +The runtime control will still function in this mode. +.PP +\f[C]notsup\f[] will cause mergerfs to return ENOTSUP for any xattr +call. +The difference with \f[C]noattr\f[] is that the kernel will cache this +fact and itself short circuit future calls. +This will be more efficient than \f[C]noattr\f[] but will cause +mergerfs\[aq] runtime control via the hidden file to stop working. .SH FUNCTIONS / POLICIES / CATEGORIES .PP The POSIX filesystem API has a number of functions. @@ -742,10 +769,6 @@ The file will not show up in \f[B]readdir\f[] but can be \f[B]stat\f[]\[aq]ed and manipulated via {list,get,set}xattrs (http://linux.die.net/man/2/listxattr) calls. .PP -Even if xattrs are disabled for mergerfs the -{list,get,set}xattrs (http://linux.die.net/man/2/listxattr) calls -against this pseudo file will still work. -.PP Any changes made at runtime are \f[B]not\f[] persisted. If you wish for values to persist they must be included as options wherever you configure the mounting of mergerfs (/etc/fstab). @@ -909,7 +932,8 @@ directories on a mergerfs mount .IP \[bu] 2 https://github.com/trapexit/scorch .IP \[bu] 2 -scorch: A tool to help discover silent corruption of files +scorch: A tool to help discover silent corruption of files and keep +track of files .IP \[bu] 2 https://github.com/trapexit/bbf .IP \[bu] 2 @@ -1567,6 +1591,9 @@ Using \f[C]direct_io\f[] will keep this from happening (and generally good to enable unless you need the features it disables) but the \f[C]security_capability\f[] option can also help by short circuiting the call and returning \f[C]ENOATTR\f[]. +.PP +You could also set \f[C]xattr\f[] to \f[C]noattr\f[] or \f[C]notsup\f[] +to short circuit or stop all xattr requests. .SS It\[aq]s mentioned that there are some security issues with mhddfs. What are they? How does mergerfs address them? .PP diff --git a/src/config.cpp b/src/config.cpp index e0c8f5cd..bf7ba0e1 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -47,6 +47,7 @@ namespace mergerfs ignorepponrename(false), security_capability(true), link_cow(false), + xattr(0), POLICYINIT(access), POLICYINIT(chmod), POLICYINIT(chown), diff --git a/src/config.hpp b/src/config.hpp index d9284067..03b12d5d 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -54,6 +54,7 @@ namespace mergerfs bool ignorepponrename; bool security_capability; bool link_cow; + int xattr; public: const Policy *policies[FuseFunc::Enum::END]; diff --git a/src/getxattr.cpp b/src/getxattr.cpp index 2dd6456c..18f7780c 100644 --- a/src/getxattr.cpp +++ b/src/getxattr.cpp @@ -140,6 +140,28 @@ _getxattr_controlfile_bool(const bool boolvalue, attrvalue = (boolvalue ? "true" : "false"); } +static +void +_getxattr_controlfile_errno(const int errno_, + string &attrvalue) +{ + switch(errno_) + { + case 0: + attrvalue = "passthrough"; + break; + case ENOATTR: + attrvalue = "noattr"; + break; + case ENOTSUP: + attrvalue = "notsup"; + break; + default: + attrvalue = "ERROR"; + break; + } +} + static void _getxattr_controlfile_policies(const Config &config, @@ -210,6 +232,8 @@ _getxattr_controlfile(const Config &config, _getxattr_controlfile_bool(config.ignorepponrename,attrvalue); else if(attr[2] == "security_capability") _getxattr_controlfile_bool(config.security_capability,attrvalue); + else if(attr[2] == "xattr") + _getxattr_controlfile_errno(config.xattr,attrvalue); else if(attr[2] == "link_cow") _getxattr_controlfile_bool(config.link_cow,attrvalue); else if(attr[2] == "policies") @@ -347,16 +371,19 @@ namespace mergerfs const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); - if((config.security_capability == false) && - _is_attrname_security_capability(attrname)) - return -ENOATTR; - if(fusepath == config.controlfile) return _getxattr_controlfile(config, attrname, buf, count); + if((config.security_capability == false) && + _is_attrname_security_capability(attrname)) + return -ENOATTR; + + if(config.xattr) + return -config.xattr; + const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.srcmountslock); diff --git a/src/listxattr.cpp b/src/listxattr.cpp index 63ab5392..4ec773e8 100644 --- a/src/listxattr.cpp +++ b/src/listxattr.cpp @@ -51,8 +51,9 @@ _listxattr_controlfile(char *list, ("user.mergerfs.symlinkify_timeout") ("user.mergerfs.nullrw") ("user.mergerfs.ignorepponrename") - ("user.mergerfs.security_capability") ("user.mergerfs.link_cow") + ("user.mergerfs.security_capability") + ("user.mergerfs.xattr") ("user.mergerfs.policies") ("user.mergerfs.version") ("user.mergerfs.pid"); @@ -115,6 +116,16 @@ namespace mergerfs if(fusepath == config.controlfile) return _listxattr_controlfile(list,size); + switch(config.xattr) + { + case 0: + break; + case ENOATTR: + return 0; + default: + return -config.xattr; + } + const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.srcmountslock); diff --git a/src/option_parser.cpp b/src/option_parser.cpp index 7cf093c0..4510a9ab 100644 --- a/src/option_parser.cpp +++ b/src/option_parser.cpp @@ -29,6 +29,7 @@ #include #include "config.hpp" +#include "errno.hpp" #include "fs_glob.hpp" #include "num.hpp" #include "policy.hpp" @@ -148,6 +149,23 @@ parse_and_process(const std::string &value, return 0; } +static +int +parse_and_process_errno(const std::string &value_, + int &errno_) +{ + if(value_ == "passthrough") + errno_ = 0; + else if(value_ == "notsup") + errno_ = ENOTSUP; + else if(value_ == "noattr") + errno_ = ENOATTR; + else + return 1; + + return 0; +} + static int parse_and_process_arg(Config &config, @@ -199,6 +217,8 @@ parse_and_process_kv_arg(Config &config, rv = parse_and_process(value,config.security_capability); else if(key == "link_cow") rv = parse_and_process(value,config.link_cow); + else if(key == "xattr") + rv = parse_and_process_errno(value,config.xattr); } if(rv == -1) @@ -305,11 +325,17 @@ usage(void) " -o ignorepponrename=\n" " Ignore path preserving when performing renames\n" " and links. default = false\n" + " -o link_cow= delink/clone file on open to simulate CoW.\n" + " default = false\n" " -o security_capability=\n" " When disabled return ENOATTR when the xattr\n" " security.capability is queried. default = true\n" - " -o link_cow= delink/clone file on open to simulate CoW.\n" - " default = false\n" + " -o xattr=passthrough|noattr|notsup\n" + " Runtime control of xattrs. By default xattr\n" + " requests will pass through to the underlying\n" + " filesystems. notattr will short circuit as if\n" + " nothing exists. notsup will respond as if not\n" + " supported or disabled. default = passthrough\n" << std::endl; } diff --git a/src/removexattr.cpp b/src/removexattr.cpp index 2bd47d36..e84f4a28 100644 --- a/src/removexattr.cpp +++ b/src/removexattr.cpp @@ -96,7 +96,9 @@ namespace mergerfs const Config &config = Config::get(fc); if(fusepath == config.controlfile) - return -ENOTSUP; + return -ENOATTR; + if(config.xattr) + return -config.xattr; const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.srcmountslock); diff --git a/src/setxattr.cpp b/src/setxattr.cpp index 7d24d088..518f10b4 100644 --- a/src/setxattr.cpp +++ b/src/setxattr.cpp @@ -33,12 +33,21 @@ #include "str.hpp" #include "ugid.hpp" +static const char SECURITY_CAPABILITY[] = "security.capability"; + using std::string; using std::vector; using mergerfs::Policy; using mergerfs::FuseFunc; using namespace mergerfs; +static +bool +_is_attrname_security_capability(const char *attrname_) +{ + return (strcmp(attrname_,SECURITY_CAPABILITY) == 0); +} + static int _add_srcmounts(vector &srcmounts, @@ -411,6 +420,13 @@ namespace mergerfs string(attrval,attrvalsize), flags); + if((config.security_capability == false) && + _is_attrname_security_capability(attrname)) + return -ENOATTR; + + if(config.xattr) + return -config.xattr; + const ugid::Set ugid(fc->uid,fc->gid); const rwlock::ReadGuard readlock(&config.srcmountslock);