Browse Source

Merge pull request #514 from trapexit/runtime_xattr

add ability to turn on/off xattr support at runtime
pull/503/merge
trapexit 6 years ago
committed by GitHub
parent
commit
602a37b231
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      README.md
  2. 39
      man/mergerfs.1
  3. 1
      src/config.cpp
  4. 1
      src/config.hpp
  5. 35
      src/getxattr.cpp
  6. 13
      src/listxattr.cpp
  7. 30
      src/option_parser.cpp
  8. 4
      src/removexattr.cpp
  9. 16
      src/setxattr.cpp

17
README.md

@ -1,6 +1,6 @@
% mergerfs(1) mergerfs user manual % mergerfs(1) mergerfs user manual
% Antonio SJ Musumeci <trapexit@spawn.link> % Antonio SJ Musumeci <trapexit@spawn.link>
% 2018-10-05
% 2018-10-08
# NAME # 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) * **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) * **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) * **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) * **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. * **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. * **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. 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 # 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). 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. 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). 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 ##### ##### 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.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 * mergerfs.mktrash: Creates FreeDesktop.org Trash specification compatible directories on a mergerfs mount
* https://github.com/trapexit/scorch * 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 * 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 * 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`. 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? #### 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. [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.

39
man/mergerfs.1

@ -1,7 +1,7 @@
.\"t .\"t
.\" Automatically generated by Pandoc 1.19.2.4 .\" 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 .hy
.SH NAME .SH NAME
.PP .PP
@ -156,6 +156,13 @@ This means files, when renamed or linked, will stay on the same drive.
xattr security.capability is queried. xattr security.capability is queried.
(default: true) (default: true)
.IP \[bu] 2 .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 \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 which has a link count > 1 it will copy the file to a temporary file and
rename over the original. 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. theoretical performance.
If an application performs much worse through mergerfs it could very 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. 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 .SH FUNCTIONS / POLICIES / CATEGORIES
.PP .PP
The POSIX filesystem API has a number of functions. 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 \f[B]stat\f[]\[aq]ed and manipulated via
{list,get,set}xattrs (http://linux.die.net/man/2/listxattr) calls. {list,get,set}xattrs (http://linux.die.net/man/2/listxattr) calls.
.PP .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. Any changes made at runtime are \f[B]not\f[] persisted.
If you wish for values to persist they must be included as options If you wish for values to persist they must be included as options
wherever you configure the mounting of mergerfs (/etc/fstab). wherever you configure the mounting of mergerfs (/etc/fstab).
@ -909,7 +932,8 @@ directories on a mergerfs mount
.IP \[bu] 2 .IP \[bu] 2
https://github.com/trapexit/scorch https://github.com/trapexit/scorch
.IP \[bu] 2 .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 .IP \[bu] 2
https://github.com/trapexit/bbf https://github.com/trapexit/bbf
.IP \[bu] 2 .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 good to enable unless you need the features it disables) but the
\f[C]security_capability\f[] option can also help by short circuiting \f[C]security_capability\f[] option can also help by short circuiting
the call and returning \f[C]ENOATTR\f[]. 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. .SS It\[aq]s mentioned that there are some security issues with mhddfs.
What are they? How does mergerfs address them? What are they? How does mergerfs address them?
.PP .PP

1
src/config.cpp

@ -47,6 +47,7 @@ namespace mergerfs
ignorepponrename(false), ignorepponrename(false),
security_capability(true), security_capability(true),
link_cow(false), link_cow(false),
xattr(0),
POLICYINIT(access), POLICYINIT(access),
POLICYINIT(chmod), POLICYINIT(chmod),
POLICYINIT(chown), POLICYINIT(chown),

1
src/config.hpp

@ -54,6 +54,7 @@ namespace mergerfs
bool ignorepponrename; bool ignorepponrename;
bool security_capability; bool security_capability;
bool link_cow; bool link_cow;
int xattr;
public: public:
const Policy *policies[FuseFunc::Enum::END]; const Policy *policies[FuseFunc::Enum::END];

35
src/getxattr.cpp

@ -140,6 +140,28 @@ _getxattr_controlfile_bool(const bool boolvalue,
attrvalue = (boolvalue ? "true" : "false"); 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 static
void void
_getxattr_controlfile_policies(const Config &config, _getxattr_controlfile_policies(const Config &config,
@ -210,6 +232,8 @@ _getxattr_controlfile(const Config &config,
_getxattr_controlfile_bool(config.ignorepponrename,attrvalue); _getxattr_controlfile_bool(config.ignorepponrename,attrvalue);
else if(attr[2] == "security_capability") else if(attr[2] == "security_capability")
_getxattr_controlfile_bool(config.security_capability,attrvalue); _getxattr_controlfile_bool(config.security_capability,attrvalue);
else if(attr[2] == "xattr")
_getxattr_controlfile_errno(config.xattr,attrvalue);
else if(attr[2] == "link_cow") else if(attr[2] == "link_cow")
_getxattr_controlfile_bool(config.link_cow,attrvalue); _getxattr_controlfile_bool(config.link_cow,attrvalue);
else if(attr[2] == "policies") else if(attr[2] == "policies")
@ -347,16 +371,19 @@ namespace mergerfs
const fuse_context *fc = fuse_get_context(); const fuse_context *fc = fuse_get_context();
const Config &config = Config::get(fc); const Config &config = Config::get(fc);
if((config.security_capability == false) &&
_is_attrname_security_capability(attrname))
return -ENOATTR;
if(fusepath == config.controlfile) if(fusepath == config.controlfile)
return _getxattr_controlfile(config, return _getxattr_controlfile(config,
attrname, attrname,
buf, buf,
count); 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 ugid::Set ugid(fc->uid,fc->gid);
const rwlock::ReadGuard readlock(&config.srcmountslock); const rwlock::ReadGuard readlock(&config.srcmountslock);

13
src/listxattr.cpp

@ -51,8 +51,9 @@ _listxattr_controlfile(char *list,
("user.mergerfs.symlinkify_timeout") ("user.mergerfs.symlinkify_timeout")
("user.mergerfs.nullrw") ("user.mergerfs.nullrw")
("user.mergerfs.ignorepponrename") ("user.mergerfs.ignorepponrename")
("user.mergerfs.security_capability")
("user.mergerfs.link_cow") ("user.mergerfs.link_cow")
("user.mergerfs.security_capability")
("user.mergerfs.xattr")
("user.mergerfs.policies") ("user.mergerfs.policies")
("user.mergerfs.version") ("user.mergerfs.version")
("user.mergerfs.pid"); ("user.mergerfs.pid");
@ -115,6 +116,16 @@ namespace mergerfs
if(fusepath == config.controlfile) if(fusepath == config.controlfile)
return _listxattr_controlfile(list,size); 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 ugid::Set ugid(fc->uid,fc->gid);
const rwlock::ReadGuard readlock(&config.srcmountslock); const rwlock::ReadGuard readlock(&config.srcmountslock);

30
src/option_parser.cpp

@ -29,6 +29,7 @@
#include <iomanip> #include <iomanip>
#include "config.hpp" #include "config.hpp"
#include "errno.hpp"
#include "fs_glob.hpp" #include "fs_glob.hpp"
#include "num.hpp" #include "num.hpp"
#include "policy.hpp" #include "policy.hpp"
@ -148,6 +149,23 @@ parse_and_process(const std::string &value,
return 0; 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 static
int int
parse_and_process_arg(Config &config, 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); rv = parse_and_process(value,config.security_capability);
else if(key == "link_cow") else if(key == "link_cow")
rv = parse_and_process(value,config.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) if(rv == -1)
@ -305,11 +325,17 @@ usage(void)
" -o ignorepponrename=<bool>\n" " -o ignorepponrename=<bool>\n"
" Ignore path preserving when performing renames\n" " Ignore path preserving when performing renames\n"
" and links. default = false\n" " and links. default = false\n"
" -o link_cow=<bool> delink/clone file on open to simulate CoW.\n"
" default = false\n"
" -o security_capability=<bool>\n" " -o security_capability=<bool>\n"
" When disabled return ENOATTR when the xattr\n" " When disabled return ENOATTR when the xattr\n"
" security.capability is queried. default = true\n" " security.capability is queried. default = true\n"
" -o link_cow=<bool> 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; << std::endl;
} }

4
src/removexattr.cpp

@ -96,7 +96,9 @@ namespace mergerfs
const Config &config = Config::get(fc); const Config &config = Config::get(fc);
if(fusepath == config.controlfile) if(fusepath == config.controlfile)
return -ENOTSUP;
return -ENOATTR;
if(config.xattr)
return -config.xattr;
const ugid::Set ugid(fc->uid,fc->gid); const ugid::Set ugid(fc->uid,fc->gid);
const rwlock::ReadGuard readlock(&config.srcmountslock); const rwlock::ReadGuard readlock(&config.srcmountslock);

16
src/setxattr.cpp

@ -33,12 +33,21 @@
#include "str.hpp" #include "str.hpp"
#include "ugid.hpp" #include "ugid.hpp"
static const char SECURITY_CAPABILITY[] = "security.capability";
using std::string; using std::string;
using std::vector; using std::vector;
using mergerfs::Policy; using mergerfs::Policy;
using mergerfs::FuseFunc; using mergerfs::FuseFunc;
using namespace mergerfs; using namespace mergerfs;
static
bool
_is_attrname_security_capability(const char *attrname_)
{
return (strcmp(attrname_,SECURITY_CAPABILITY) == 0);
}
static static
int int
_add_srcmounts(vector<string> &srcmounts, _add_srcmounts(vector<string> &srcmounts,
@ -411,6 +420,13 @@ namespace mergerfs
string(attrval,attrvalsize), string(attrval,attrvalsize),
flags); 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 ugid::Set ugid(fc->uid,fc->gid);
const rwlock::ReadGuard readlock(&config.srcmountslock); const rwlock::ReadGuard readlock(&config.srcmountslock);

Loading…
Cancel
Save