Browse Source

add ability to change statfs behavior

pull/529/head
Antonio SJ Musumeci 6 years ago
parent
commit
680f8194f9
  1. 2
      README.md
  2. 21
      man/mergerfs.1
  3. 6
      src/branch.cpp
  4. 1
      src/branch.hpp
  5. 2
      src/config.cpp
  6. 22
      src/config.hpp
  7. 31
      src/fs_base_statvfs.hpp
  8. 45
      src/getxattr.cpp
  9. 2
      src/listxattr.cpp
  10. 49
      src/option_parser.cpp
  11. 73
      src/setxattr.cpp
  12. 88
      src/statfs.cpp

2
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**

21
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

6
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
{

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

2
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),

22
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];

31
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 <sys/statvfs.h>
@ -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;
}
}

45
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")

2
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");

49
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<std::string> 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;
}

73
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:

88
src/statfs.cpp

@ -1,5 +1,5 @@
/*
Copyright (c) 2016, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2018, Antonio SJ Musumeci <trapexit@spawn.link>
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 <fuse.h>
#include <algorithm>
#include <climits>
#include <limits>
#include <map>
#include <string>
#include <vector>
@ -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
@ -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<dev_t,struct statvfs> &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_,
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<dev_t,struct statvfs> fsstats;
unsigned long min_bsize = ULONG_MAX;
unsigned long min_frsize = ULONG_MAX;
unsigned long min_namemax = ULONG_MAX;
min_bsize = std::numeric_limits<unsigned long>::max();
min_frsize = std::numeric_limits<unsigned long>::max();
min_namemax = std::numeric_limits<unsigned long>::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<dev_t,struct statvfs>::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);
}
}

Loading…
Cancel
Save