mirror of
https://github.com/trapexit/mergerfs.git
synced 2024-11-25 09:03:37 +08:00
parent
e0f0bb53f7
commit
12f393a55e
82
README.md
82
README.md
|
@ -8,7 +8,7 @@ mergerfs - another FUSE union filesystem
|
|||
|
||||
# SYNOPSIS
|
||||
|
||||
mergerfs -ocreate=epmfs,search=ff <srcpoints> <mountpoint>
|
||||
mergerfs -o<options> <srcpoints> <mountpoint>
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
|
@ -20,10 +20,11 @@ Why create mergerfs when those exist? mhddfs isn't really maintained or flexible
|
|||
|
||||
###options###
|
||||
|
||||
| Option | Default |
|
||||
|--------|--------|
|
||||
| search | ff |
|
||||
| create | epmfs |
|
||||
All [FUSE](http://fuse.sourceforge.net) functions which have a category (see below) are option keys. The syntax being `<func>=<policy>`.
|
||||
|
||||
To set all function policies in a category use `category.<category>=<policy>` such as `category.create=mfs`.
|
||||
|
||||
They are evaluated in the order listed so if the options are `rmdir=rand,category.action=ff` the `action` category setting will override the `rmdir` setting.
|
||||
|
||||
###srcpoints###
|
||||
|
||||
|
@ -46,14 +47,15 @@ In /etc/fstab it'd look like the following:
|
|||
|
||||
# POLICIES
|
||||
|
||||
Filesystem calls are broken up into 2 categories: search and create. There are also some calls which have no policy attached due to state being kept between calls. These categories can be assigned a policy which dictates how [mergerfs](http://github.com/trapexit/mergerfs) behaves. Any policy can be assigned to a category though some aren't terribly practical. For instance: rand (Random) may be useful for **create** but could lead to very odd behavior if used for **search**.
|
||||
Filesystem calls are broken up into 3 categories: action, create, search. There are also some calls which have no policy attached due to state being kept between calls. These categories can be assigned a policy which dictates how [mergerfs](http://github.com/trapexit/mergerfs) behaves. Any policy can be assigned to a category though some aren't terribly practical. For instance: rand (Random) may be useful for **create** but could lead to very odd behavior if used for **search**.
|
||||
|
||||
#### Functional classifications ####
|
||||
| Class | FUSE calls |
|
||||
|-------|------------|
|
||||
| search | access, getattr, getxattr, listxattr, open, readlink, chmod, link, removexattr, rmdir, setxattr, truncate, unlink, utimens |
|
||||
| action | chmod, chown, link, removexattr, rename, rmdir, setxattr, truncate, unlink, utimens |
|
||||
| search | access, getattr, getxattr, listxattr, open, readlink, symlink |
|
||||
| create | create, mkdir, mknod |
|
||||
| none | fallocate, fgetattr, fsync, ftruncate, ioctl, read, readdir, rename, statfs, symlink, write, release |
|
||||
| N/A | fallocate, fgetattr, fsync, ftruncate, ioctl, read, readdir, statfs, symlink, write, release |
|
||||
|
||||
#### Policy descriptions ####
|
||||
| Policy | Description |
|
||||
|
@ -100,22 +102,66 @@ There is a pseudo file available at the mountpoint which allows for the runtime
|
|||
|
||||
Even if xattrs are disabled the [{list,get,set}xattrs](http://linux.die.net/man/2/listxattr) calls will still work.
|
||||
|
||||
The keys are:
|
||||
##### Keys #####
|
||||
* user.mergerfs.srcmounts
|
||||
* user.mergerfs.create
|
||||
* user.mergerfs.search
|
||||
* user.mergerfs.category.action
|
||||
* user.mergerfs.category.create
|
||||
* user.mergerfs.category.search
|
||||
* user.mergerfs.func.access
|
||||
* user.mergerfs.func.chmod
|
||||
* user.mergerfs.func.chown
|
||||
* user.mergerfs.func.create
|
||||
* user.mergerfs.func.getattr
|
||||
* user.mergerfs.func.getxattr
|
||||
* user.mergerfs.func.link
|
||||
* user.mergerfs.func.listxattr
|
||||
* user.mergerfs.func.mkdir
|
||||
* user.mergerfs.func.mknod
|
||||
* user.mergerfs.func.open
|
||||
* user.mergerfs.func.readlink
|
||||
* user.mergerfs.func.removexattr
|
||||
* user.mergerfs.func.rename
|
||||
* user.mergerfs.func.rmdir
|
||||
* user.mergerfs.func.setxattr
|
||||
* user.mergerfs.func.symlink
|
||||
* user.mergerfs.func.truncate
|
||||
* user.mergerfs.func.unlink
|
||||
* user.mergerfs.func.utimens
|
||||
|
||||
##### Example #####
|
||||
|
||||
```
|
||||
[trapexit:/tmp/mount] $ xattr -l .mergerfs
|
||||
user.mergerfs.srcmounts: /tmp/a:/tmp/b
|
||||
user.mergerfs.create: epmfs
|
||||
user.mergerfs.search: ff
|
||||
user.mergerfs.category.action: ff
|
||||
user.mergerfs.category.create: epmfs
|
||||
user.mergerfs.category.search: ff
|
||||
user.mergerfs.func.access: ff
|
||||
user.mergerfs.func.chmod: ff
|
||||
user.mergerfs.func.chown: ff
|
||||
user.mergerfs.func.create: epmfs
|
||||
user.mergerfs.func.getattr: ff
|
||||
user.mergerfs.func.getxattr: ff
|
||||
user.mergerfs.func.link: ff
|
||||
user.mergerfs.func.listxattr: ff
|
||||
user.mergerfs.func.mkdir: epmfs
|
||||
user.mergerfs.func.mknod: epmfs
|
||||
user.mergerfs.func.open: ff
|
||||
user.mergerfs.func.readlink: ff
|
||||
user.mergerfs.func.removexattr: ff
|
||||
user.mergerfs.func.rename: ff
|
||||
user.mergerfs.func.rmdir: ff
|
||||
user.mergerfs.func.setxattr: ff
|
||||
user.mergerfs.func.symlink: ff
|
||||
user.mergerfs.func.truncate: ff
|
||||
user.mergerfs.func.unlink: ff
|
||||
user.mergerfs.func.utimens: ff
|
||||
|
||||
[trapexit:/tmp/mount] $ xattr -p user.mergerfs.search .mergerfs
|
||||
[trapexit:/tmp/mount] $ xattr -p user.mergerfs.category.search .mergerfs
|
||||
ff
|
||||
|
||||
[trapexit:/tmp/mount] $ xattr -w user.mergerfs.search ffwp .mergerfs
|
||||
[trapexit:/tmp/mount] $ xattr -p user.mergerfs.search .mergerfs
|
||||
[trapexit:/tmp/mount] $ xattr -w user.mergerfs.category.search ffwp .mergerfs
|
||||
[trapexit:/tmp/mount] $ xattr -p user.mergerfs.category.search .mergerfs
|
||||
ffwp
|
||||
|
||||
[trapexit:/tmp/mount] $ xattr -w user.mergerfs.srcmounts +/tmp/c .mergerfs
|
||||
|
@ -131,6 +177,8 @@ ffwp
|
|||
/tmp/a:/tmp/b:/tmp/c
|
||||
```
|
||||
|
||||
##### Extra Details #####
|
||||
|
||||
For **user.mergerfs.srcmounts** there are several instructions available for manipulating the list. The value provided is just as the value used at mount time. A colon (':') delimited list of full path globs.
|
||||
|
||||
| Instruction | Description |
|
||||
|
@ -144,6 +192,8 @@ For **user.mergerfs.srcmounts** there are several instructions available for man
|
|||
| =[list] | set |
|
||||
| [list] | set |
|
||||
|
||||
Categories and funcs take a policy as described in the previous section. When reading funcs you'll get the policy string. However, with categories you'll get a coma separated list of policies for each type found. For example: if all search functions are `ff` except for `access` which is `ffwp` the value for `user.mergerfs.category.search` will be `ff,ffwp`.
|
||||
|
||||
#### mergerfs file xattrs ####
|
||||
|
||||
While they won't show up when using [listxattr](http://linux.die.net/man/2/listxattr) mergerfs offers a number of special xattrs to query information about the files served. To access the values you will need to issue a [getxattr](http://linux.die.net/man/2/getxattr) for one of the following:
|
||||
|
|
|
@ -75,7 +75,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _access(*config.search,
|
||||
return _access(*config.access,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
mask);
|
||||
|
|
|
@ -35,12 +35,14 @@ namespace mergerfs
|
|||
const std::vector<Category> Category::_categories_ =
|
||||
buildvector<Category,true>
|
||||
(CATEGORY(invalid))
|
||||
(CATEGORY(action))
|
||||
(CATEGORY(create))
|
||||
(CATEGORY(search));
|
||||
|
||||
const Category * const Category::categories = &_categories_[1];
|
||||
|
||||
const Category &Category::invalid = Category::categories[Category::Enum::invalid];
|
||||
const Category &Category::action = Category::categories[Category::Enum::action];
|
||||
const Category &Category::create = Category::categories[Category::Enum::create];
|
||||
const Category &Category::search = Category::categories[Category::Enum::search];
|
||||
|
||||
|
|
|
@ -39,7 +39,8 @@ namespace mergerfs
|
|||
{
|
||||
invalid = -1,
|
||||
BEGIN = 0,
|
||||
create = BEGIN,
|
||||
action = BEGIN,
|
||||
create,
|
||||
search,
|
||||
END
|
||||
};
|
||||
|
@ -88,6 +89,7 @@ namespace mergerfs
|
|||
static const std::vector<Category> _categories_;
|
||||
static const Category * const categories;
|
||||
static const Category &invalid;
|
||||
static const Category &action;
|
||||
static const Category &create;
|
||||
static const Category &search;
|
||||
};
|
||||
|
|
|
@ -69,7 +69,7 @@ namespace mergerfs
|
|||
const config::Config &config = config::get();
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _chmod(*config.search,
|
||||
return _chmod(*config.chmod,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
mode);
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _chown(*config.search,
|
||||
return _chown(*config.chown,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
uid,
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
#include "rwlock.hpp"
|
||||
#include "fs.hpp"
|
||||
|
||||
#define POLICYINIT(X) X(policies[FuseFunc::Enum::X])
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
|
@ -43,14 +45,46 @@ namespace mergerfs
|
|||
: destmount(),
|
||||
srcmounts(),
|
||||
srcmountslock(),
|
||||
create(policies[Category::Enum::create]),
|
||||
search(policies[Category::Enum::search]),
|
||||
POLICYINIT(access),
|
||||
POLICYINIT(chmod),
|
||||
POLICYINIT(chown),
|
||||
POLICYINIT(create),
|
||||
POLICYINIT(getattr),
|
||||
POLICYINIT(getxattr),
|
||||
POLICYINIT(link),
|
||||
POLICYINIT(listxattr),
|
||||
POLICYINIT(mkdir),
|
||||
POLICYINIT(mknod),
|
||||
POLICYINIT(open),
|
||||
POLICYINIT(readlink),
|
||||
POLICYINIT(removexattr),
|
||||
POLICYINIT(rename),
|
||||
POLICYINIT(rmdir),
|
||||
POLICYINIT(setxattr),
|
||||
POLICYINIT(symlink),
|
||||
POLICYINIT(truncate),
|
||||
POLICYINIT(unlink),
|
||||
POLICYINIT(utimens),
|
||||
controlfile("/.mergerfs")
|
||||
{
|
||||
pthread_rwlock_init(&srcmountslock,NULL);
|
||||
|
||||
create = &Policy::epmfs;
|
||||
search = &Policy::ff;
|
||||
setpolicy(Category::Enum::action,Policy::Enum::ff);
|
||||
setpolicy(Category::Enum::create,Policy::Enum::epmfs);
|
||||
setpolicy(Category::Enum::search,Policy::Enum::ff);
|
||||
}
|
||||
|
||||
void
|
||||
Config::setpolicy(const Category::Enum::Type category,
|
||||
const Policy::Enum::Type policy_)
|
||||
{
|
||||
const Policy *policy = Policy::find(policy_);
|
||||
|
||||
for(int i = 0; i < FuseFunc::Enum::END; i++)
|
||||
{
|
||||
if(FuseFunc::fusefuncs[i] == category)
|
||||
policies[(FuseFunc::Enum::Type)FuseFunc::fusefuncs[i]] = policy;
|
||||
}
|
||||
}
|
||||
|
||||
const Config&
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "policy.hpp"
|
||||
#include "category.hpp"
|
||||
#include "fusefunc.hpp"
|
||||
|
||||
namespace mergerfs
|
||||
{
|
||||
|
@ -44,14 +44,37 @@ namespace mergerfs
|
|||
public:
|
||||
Config();
|
||||
|
||||
public:
|
||||
void setpolicy(const Category::Enum::Type category,
|
||||
const Policy::Enum::Type policy);
|
||||
|
||||
public:
|
||||
std::string destmount;
|
||||
std::vector<std::string> srcmounts;
|
||||
mutable pthread_rwlock_t srcmountslock;
|
||||
|
||||
const Policy *policies[Category::Enum::END];
|
||||
public:
|
||||
const Policy *policies[FuseFunc::Enum::END];
|
||||
const Policy *&access;
|
||||
const Policy *&chmod;
|
||||
const Policy *&chown;
|
||||
const Policy *&create;
|
||||
const Policy *&search;
|
||||
const Policy *&getattr;
|
||||
const Policy *&getxattr;
|
||||
const Policy *&link;
|
||||
const Policy *&listxattr;
|
||||
const Policy *&mkdir;
|
||||
const Policy *&mknod;
|
||||
const Policy *&open;
|
||||
const Policy *&readlink;
|
||||
const Policy *&removexattr;
|
||||
const Policy *&rename;
|
||||
const Policy *&rmdir;
|
||||
const Policy *&setxattr;
|
||||
const Policy *&symlink;
|
||||
const Policy *&truncate;
|
||||
const Policy *&unlink;
|
||||
const Policy *&utimens;
|
||||
|
||||
public:
|
||||
const std::string controlfile;
|
||||
|
|
|
@ -100,7 +100,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _create(*config.search,
|
||||
return _create(*config.create,
|
||||
*config.create,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
|
|
29
src/fs.cpp
29
src/fs.cpp
|
@ -39,7 +39,6 @@
|
|||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <glob.h>
|
||||
#include <fnmatch.h>
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "xattr.hpp"
|
||||
|
@ -492,7 +491,7 @@ namespace fs
|
|||
if(rv == -1 && errno != ENOTTY)
|
||||
return -1;
|
||||
|
||||
return (errno = 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -515,32 +514,6 @@ namespace fs
|
|||
globfree(&gbuf);
|
||||
}
|
||||
|
||||
void
|
||||
erase_fnmatches(const vector<string> &patterns,
|
||||
vector<string> &strs)
|
||||
{
|
||||
vector<string>::iterator si;
|
||||
vector<string>::const_iterator pi;
|
||||
|
||||
si = strs.begin();
|
||||
while(si != strs.end())
|
||||
{
|
||||
int match = FNM_NOMATCH;
|
||||
|
||||
for(pi = patterns.begin();
|
||||
pi != patterns.end() && match != 0;
|
||||
++pi)
|
||||
{
|
||||
match = fnmatch(pi->c_str(),si->c_str(),0);
|
||||
}
|
||||
|
||||
if(match == 0)
|
||||
si = strs.erase(si);
|
||||
else
|
||||
++si;
|
||||
}
|
||||
}
|
||||
|
||||
namespace find
|
||||
{
|
||||
int
|
||||
|
|
|
@ -110,9 +110,6 @@ namespace fs
|
|||
void glob(const vector<string> &patterns,
|
||||
vector<string> &strs);
|
||||
|
||||
void erase_fnmatches(const vector<string> &patterns,
|
||||
vector<string> &strs);
|
||||
|
||||
namespace find
|
||||
{
|
||||
int invalid(const vector<string> &basepaths,
|
||||
|
|
105
src/fusefunc.cpp
Normal file
105
src/fusefunc.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "fusefunc.hpp"
|
||||
#include "category.hpp"
|
||||
#include "buildvector.hpp"
|
||||
|
||||
#define FUSEFUNC(X,Y) FuseFunc(FuseFunc::Enum::X,#X,Category::Enum::Y)
|
||||
|
||||
namespace mergerfs
|
||||
{
|
||||
const std::vector<FuseFunc> FuseFunc::_fusefuncs_ =
|
||||
buildvector<FuseFunc,true>
|
||||
(FUSEFUNC(invalid,invalid))
|
||||
(FUSEFUNC(access,search))
|
||||
(FUSEFUNC(chmod,action))
|
||||
(FUSEFUNC(chown,action))
|
||||
(FUSEFUNC(create,create))
|
||||
(FUSEFUNC(getattr,search))
|
||||
(FUSEFUNC(getxattr,search))
|
||||
(FUSEFUNC(link,action))
|
||||
(FUSEFUNC(listxattr,search))
|
||||
(FUSEFUNC(mkdir,create))
|
||||
(FUSEFUNC(mknod,create))
|
||||
(FUSEFUNC(open,search))
|
||||
(FUSEFUNC(readlink,search))
|
||||
(FUSEFUNC(removexattr,action))
|
||||
(FUSEFUNC(rename,action))
|
||||
(FUSEFUNC(rmdir,action))
|
||||
(FUSEFUNC(setxattr,action))
|
||||
(FUSEFUNC(symlink,search))
|
||||
(FUSEFUNC(truncate,action))
|
||||
(FUSEFUNC(unlink,action))
|
||||
(FUSEFUNC(utimens,action))
|
||||
;
|
||||
|
||||
const FuseFunc * const FuseFunc::fusefuncs = &_fusefuncs_[1];
|
||||
|
||||
const FuseFunc &FuseFunc::invalid = FuseFunc::fusefuncs[FuseFunc::Enum::invalid];
|
||||
const FuseFunc &FuseFunc::access = FuseFunc::fusefuncs[FuseFunc::Enum::access];
|
||||
const FuseFunc &FuseFunc::chmod = FuseFunc::fusefuncs[FuseFunc::Enum::chmod];
|
||||
const FuseFunc &FuseFunc::chown = FuseFunc::fusefuncs[FuseFunc::Enum::chown];
|
||||
const FuseFunc &FuseFunc::create = FuseFunc::fusefuncs[FuseFunc::Enum::create];
|
||||
const FuseFunc &FuseFunc::getattr = FuseFunc::fusefuncs[FuseFunc::Enum::getattr];
|
||||
const FuseFunc &FuseFunc::getxattr = FuseFunc::fusefuncs[FuseFunc::Enum::getxattr];
|
||||
const FuseFunc &FuseFunc::link = FuseFunc::fusefuncs[FuseFunc::Enum::link];
|
||||
const FuseFunc &FuseFunc::listxattr = FuseFunc::fusefuncs[FuseFunc::Enum::listxattr];
|
||||
const FuseFunc &FuseFunc::mkdir = FuseFunc::fusefuncs[FuseFunc::Enum::mkdir];
|
||||
const FuseFunc &FuseFunc::mknod = FuseFunc::fusefuncs[FuseFunc::Enum::mknod];
|
||||
const FuseFunc &FuseFunc::open = FuseFunc::fusefuncs[FuseFunc::Enum::open];
|
||||
const FuseFunc &FuseFunc::readlink = FuseFunc::fusefuncs[FuseFunc::Enum::readlink];
|
||||
const FuseFunc &FuseFunc::removexattr = FuseFunc::fusefuncs[FuseFunc::Enum::removexattr];
|
||||
const FuseFunc &FuseFunc::rmdir = FuseFunc::fusefuncs[FuseFunc::Enum::rmdir];
|
||||
const FuseFunc &FuseFunc::setxattr = FuseFunc::fusefuncs[FuseFunc::Enum::setxattr];
|
||||
const FuseFunc &FuseFunc::symlink = FuseFunc::fusefuncs[FuseFunc::Enum::symlink];
|
||||
const FuseFunc &FuseFunc::truncate = FuseFunc::fusefuncs[FuseFunc::Enum::truncate];
|
||||
const FuseFunc &FuseFunc::unlink = FuseFunc::fusefuncs[FuseFunc::Enum::unlink];
|
||||
const FuseFunc &FuseFunc::utimens = FuseFunc::fusefuncs[FuseFunc::Enum::utimens];
|
||||
|
||||
const FuseFunc&
|
||||
FuseFunc::find(const std::string &str)
|
||||
{
|
||||
for(int i = Enum::BEGIN; i != Enum::END; ++i)
|
||||
{
|
||||
if(fusefuncs[i] == str)
|
||||
return fusefuncs[i];
|
||||
}
|
||||
|
||||
return invalid;
|
||||
}
|
||||
|
||||
const FuseFunc&
|
||||
FuseFunc::find(const FuseFunc::Enum::Type i)
|
||||
{
|
||||
if(i >= FuseFunc::Enum::BEGIN &&
|
||||
i < FuseFunc::Enum::END)
|
||||
return fusefuncs[i];
|
||||
|
||||
return invalid;
|
||||
}
|
||||
}
|
139
src/fusefunc.hpp
Normal file
139
src/fusefunc.hpp
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Antonio SJ Musumeci <trapexit@spawn.link>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __FUSEFUNC_HPP__
|
||||
#define __FUSEFUNC_HPP__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "category.hpp"
|
||||
|
||||
namespace mergerfs
|
||||
{
|
||||
class FuseFunc
|
||||
{
|
||||
public:
|
||||
struct Enum
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
invalid = -1,
|
||||
BEGIN = 0,
|
||||
access = BEGIN,
|
||||
chmod,
|
||||
chown,
|
||||
create,
|
||||
getattr,
|
||||
getxattr,
|
||||
link,
|
||||
listxattr,
|
||||
mkdir,
|
||||
mknod,
|
||||
open,
|
||||
readlink,
|
||||
removexattr,
|
||||
rename,
|
||||
rmdir,
|
||||
setxattr,
|
||||
symlink,
|
||||
truncate,
|
||||
unlink,
|
||||
utimens,
|
||||
END
|
||||
};
|
||||
};
|
||||
|
||||
private:
|
||||
Enum::Type _enum;
|
||||
std::string _str;
|
||||
Category::Enum::Type _category;
|
||||
|
||||
public:
|
||||
FuseFunc()
|
||||
: _enum(invalid),
|
||||
_str(invalid),
|
||||
_category(Category::Enum::invalid)
|
||||
{
|
||||
}
|
||||
|
||||
FuseFunc(const Enum::Type enum_,
|
||||
const std::string &str_,
|
||||
const Category::Enum::Type category_)
|
||||
: _enum(enum_),
|
||||
_str(str_),
|
||||
_category(category_)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
operator const Enum::Type() const { return _enum; }
|
||||
operator const std::string&() const { return _str; }
|
||||
operator const Category::Enum::Type() const { return _category; }
|
||||
operator const FuseFunc*() const { return this; }
|
||||
|
||||
bool operator==(const std::string &str_) const
|
||||
{ return _str == str_; }
|
||||
|
||||
bool operator==(const Enum::Type enum_) const
|
||||
{ return _enum == enum_; }
|
||||
|
||||
bool operator!=(const FuseFunc &r) const
|
||||
{ return _enum != r._enum; }
|
||||
|
||||
bool operator<(const FuseFunc &r) const
|
||||
{ return _enum < r._enum; }
|
||||
|
||||
public:
|
||||
static const FuseFunc &find(const std::string&);
|
||||
static const FuseFunc &find(const Enum::Type);
|
||||
|
||||
public:
|
||||
static const std::vector<FuseFunc> _fusefuncs_;
|
||||
static const FuseFunc * const fusefuncs;
|
||||
static const FuseFunc &invalid;
|
||||
static const FuseFunc &access;
|
||||
static const FuseFunc &chmod;
|
||||
static const FuseFunc &chown;
|
||||
static const FuseFunc &create;
|
||||
static const FuseFunc &getattr;
|
||||
static const FuseFunc &getxattr;
|
||||
static const FuseFunc &link;
|
||||
static const FuseFunc &listxattr;
|
||||
static const FuseFunc &mkdir;
|
||||
static const FuseFunc &mknod;
|
||||
static const FuseFunc &open;
|
||||
static const FuseFunc &readlink;
|
||||
static const FuseFunc &removexattr;
|
||||
static const FuseFunc &rename;
|
||||
static const FuseFunc &rmdir;
|
||||
static const FuseFunc &setxattr;
|
||||
static const FuseFunc &symlink;
|
||||
static const FuseFunc &truncate;
|
||||
static const FuseFunc &unlink;
|
||||
static const FuseFunc &utimens;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -99,7 +99,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _getattr(*config.search,
|
||||
return _getattr(*config.getattr,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
*st);
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
|
@ -40,24 +42,76 @@
|
|||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::set;
|
||||
using namespace mergerfs;
|
||||
using namespace mergerfs::config;
|
||||
|
||||
static
|
||||
void
|
||||
_getxattr_controlfile_fusefunc_policy(const Config &config,
|
||||
const char *attrbasename,
|
||||
string &attrvalue)
|
||||
{
|
||||
FuseFunc fusefunc;
|
||||
|
||||
fusefunc = FuseFunc::find(attrbasename);
|
||||
if(fusefunc != FuseFunc::invalid)
|
||||
attrvalue = (std::string)*config.policies[(FuseFunc::Enum::Type)*fusefunc];
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
_getxattr_controlfile_category_policy(const Config &config,
|
||||
const char *attrbasename,
|
||||
string &attrvalue)
|
||||
{
|
||||
Category cat;
|
||||
|
||||
cat = Category::find(attrbasename);
|
||||
if(cat != Category::invalid)
|
||||
{
|
||||
vector<string> policies;
|
||||
for(int i = FuseFunc::Enum::BEGIN; i < FuseFunc::Enum::END; i++)
|
||||
{
|
||||
if(cat == (Category::Enum::Type)*FuseFunc::fusefuncs[i])
|
||||
policies.push_back(*config.policies[i]);
|
||||
}
|
||||
|
||||
std::sort(policies.begin(),policies.end());
|
||||
policies.erase(std::unique(policies.begin(),policies.end()),
|
||||
policies.end());
|
||||
attrvalue = str::join(policies,',');
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
_getxattr_controlfile_srcmounts(const Config &config,
|
||||
string &attrvalue)
|
||||
{
|
||||
attrvalue = str::join(config.srcmounts,':');
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
_getxattr_controlfile(const Config &config,
|
||||
const string &attrname,
|
||||
const char *attrname,
|
||||
char *buf,
|
||||
const size_t count)
|
||||
{
|
||||
size_t len;
|
||||
string attrvalue;
|
||||
const char *attrbasename = &attrname[sizeof("user.mergerfs.")-1];
|
||||
|
||||
if(attrname == "user.mergerfs.create")
|
||||
attrvalue = (std::string)*config.create;
|
||||
else if(attrname == "user.mergerfs.search")
|
||||
attrvalue = (std::string)*config.search;
|
||||
else if(attrname == "user.mergerfs.srcmounts")
|
||||
attrvalue = str::join(config.srcmounts,':');
|
||||
if(strncmp("user.mergerfs.",attrname,sizeof("user.mergerfs.")-1))
|
||||
return -ENOATTR;
|
||||
|
||||
if(!strcmp("srcmounts",attrbasename))
|
||||
_getxattr_controlfile_srcmounts(config,attrvalue);
|
||||
else if(!strncmp("category.",attrbasename,sizeof("category.")-1))
|
||||
_getxattr_controlfile_category_policy(config,&attrbasename[sizeof("category.")-1],attrvalue);
|
||||
else if(!strncmp("func.",attrbasename,sizeof("func.")-1))
|
||||
_getxattr_controlfile_fusefunc_policy(config,&attrbasename[sizeof("func.")-1],attrvalue);
|
||||
|
||||
if(attrvalue.empty())
|
||||
return -ENOATTR;
|
||||
|
@ -185,7 +239,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _getxattr(*config.search,
|
||||
return _getxattr(*config.getxattr,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
attrname,
|
||||
|
|
|
@ -123,7 +123,7 @@ _ioctl_dir(const string &fusepath,
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _ioctl_dir_base(*config.search,
|
||||
return _ioctl_dir_base(*config.getattr,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
cmd,
|
||||
|
|
|
@ -91,7 +91,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _link(*config.search,
|
||||
return _link(*config.link,
|
||||
config.srcmounts,
|
||||
from,
|
||||
to);
|
||||
|
|
|
@ -47,21 +47,24 @@ int
|
|||
_listxattr_controlfile(char *list,
|
||||
const size_t size)
|
||||
{
|
||||
const char xattrs[] =
|
||||
"user.mergerfs.srcmounts\0"
|
||||
"user.mergerfs.create\0"
|
||||
"user.mergerfs.search"
|
||||
;
|
||||
string xattrs;
|
||||
|
||||
xattrs.reserve(512);
|
||||
xattrs.append("user.mergerfs.srcmounts",sizeof("user.mergerfs.srcmounts"));
|
||||
for(int i = Category::Enum::BEGIN; i < Category::Enum::END; i++)
|
||||
xattrs += ("user.mergerfs.category." + (std::string)*Category::categories[i] + '\0');
|
||||
for(int i = FuseFunc::Enum::BEGIN; i < FuseFunc::Enum::END; i++)
|
||||
xattrs += ("user.mergerfs.func." + (std::string)*FuseFunc::fusefuncs[i] + '\0');
|
||||
|
||||
if(size == 0)
|
||||
return sizeof(xattrs);
|
||||
return xattrs.size();
|
||||
|
||||
if(size < sizeof(xattrs))
|
||||
if(size < xattrs.size())
|
||||
return -ERANGE;
|
||||
|
||||
memcpy(list,xattrs,sizeof(xattrs));
|
||||
memcpy(list,xattrs.c_str(),xattrs.size());
|
||||
|
||||
return sizeof(xattrs);
|
||||
return xattrs.size();
|
||||
}
|
||||
|
||||
static
|
||||
|
@ -107,7 +110,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _listxattr(*config.search,
|
||||
return _listxattr(*config.listxattr,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
list,
|
||||
|
|
|
@ -92,8 +92,8 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _mkdir(*config.search,
|
||||
*config.create,
|
||||
return _mkdir(*config.getattr,
|
||||
*config.mkdir,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
(mode & ~fc->umask));
|
||||
|
|
|
@ -95,8 +95,8 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _mknod(*config.search,
|
||||
*config.create,
|
||||
return _mknod(*config.getattr,
|
||||
*config.mknod,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
(mode & ~fc->umask),
|
||||
|
|
|
@ -79,7 +79,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _open(*config.search,
|
||||
return _open(*config.open,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
fileinfo->flags,
|
||||
|
|
|
@ -53,12 +53,13 @@ process_opt(config::Config &config,
|
|||
switch(argvalue.size())
|
||||
{
|
||||
case 2:
|
||||
if(argvalue[0] == "create")
|
||||
config.create = Policy::find(argvalue[1]);
|
||||
else if(argvalue[0] == "search")
|
||||
config.search = Policy::find(argvalue[1]);
|
||||
else
|
||||
rv = 1;
|
||||
{
|
||||
FuseFunc fusefunc = FuseFunc::find(argvalue[0]);
|
||||
if(fusefunc != FuseFunc::invalid)
|
||||
config.policies[(FuseFunc::Enum::Type)*fusefunc] = Policy::find(argvalue[1]);
|
||||
else
|
||||
rv = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
@ -77,7 +77,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _readlink(*config.search,
|
||||
return _readlink(*config.readlink,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
buf,
|
||||
|
|
|
@ -79,7 +79,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _removexattr(*config.search,
|
||||
return _removexattr(*config.removexattr,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
attrname);
|
||||
|
|
|
@ -76,7 +76,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _rename(*config.search,
|
||||
return _rename(*config.rename,
|
||||
config.srcmounts,
|
||||
from,
|
||||
to);
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readguard(&config.srcmountslock);
|
||||
|
||||
return _rmdir(*config.search,
|
||||
return _rmdir(*config.rmdir,
|
||||
config.srcmounts,
|
||||
fusepath);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.hpp"
|
||||
#include "fs.hpp"
|
||||
|
@ -41,7 +42,7 @@
|
|||
using std::string;
|
||||
using std::vector;
|
||||
using mergerfs::Policy;
|
||||
using mergerfs::Category;
|
||||
using mergerfs::FuseFunc;
|
||||
using namespace mergerfs;
|
||||
|
||||
static
|
||||
|
@ -86,7 +87,7 @@ _erase_srcmounts(vector<string> &srcmounts,
|
|||
{
|
||||
const rwlock::WriteGuard wrg(&srcmountslock);
|
||||
|
||||
fs::erase_fnmatches(patterns,srcmounts);
|
||||
str::erase_fnmatches(patterns,srcmounts);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -166,26 +167,54 @@ _setxattr_srcmounts(vector<string> &srcmounts,
|
|||
|
||||
static
|
||||
int
|
||||
_setxattr_policy(const Policy *policies[],
|
||||
const string &attrname,
|
||||
const string &attrval,
|
||||
const int flags)
|
||||
_setxattr_controlfile_func_policy(const Policy *policies[],
|
||||
const char *funcname,
|
||||
const string &attrval,
|
||||
const int flags)
|
||||
{
|
||||
const Category *cat;
|
||||
const FuseFunc *fusefunc;
|
||||
const Policy *policy;
|
||||
|
||||
cat = Category::find(attrname);
|
||||
if(cat == Category::invalid)
|
||||
fusefunc = FuseFunc::find(funcname);
|
||||
if(fusefunc == FuseFunc::invalid)
|
||||
return -ENOATTR;
|
||||
|
||||
if((flags & XATTR_CREATE) == XATTR_CREATE)
|
||||
return -EEXIST;
|
||||
|
||||
policy = Policy::find(attrval);
|
||||
if(policy == Policy::invalid)
|
||||
if((policy == Policy::invalid) &&
|
||||
(attrval != "invalid"))
|
||||
return -EINVAL;
|
||||
|
||||
policies[*cat] = policy;
|
||||
policies[(FuseFunc::Enum::Type)*fusefunc] = policy;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static
|
||||
int
|
||||
_setxattr_controlfile_category_policy(config::Config &config,
|
||||
const char *categoryname,
|
||||
const string &attrval,
|
||||
const int flags)
|
||||
{
|
||||
const Category *category;
|
||||
const Policy *policy;
|
||||
|
||||
category = Category::find(categoryname);
|
||||
if(category == Category::invalid)
|
||||
return -ENOATTR;
|
||||
|
||||
if((flags & XATTR_CREATE) == XATTR_CREATE)
|
||||
return -EEXIST;
|
||||
|
||||
policy = Policy::find(attrval);
|
||||
if((policy == Policy::invalid) &&
|
||||
(attrval != "invalid"))
|
||||
return -EINVAL;
|
||||
|
||||
config.setpolicy(*category,*policy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -193,33 +222,36 @@ _setxattr_policy(const Policy *policies[],
|
|||
static
|
||||
int
|
||||
_setxattr_controlfile(config::Config &config,
|
||||
const string &attrname,
|
||||
const char *attrname,
|
||||
const string &attrval,
|
||||
const int flags)
|
||||
{
|
||||
vector<string> nameparts;
|
||||
const char *attrbasename = &attrname[sizeof("user.mergerfs.")-1];
|
||||
|
||||
str::split(nameparts,attrname,'.');
|
||||
|
||||
if(nameparts.size() != 3 ||
|
||||
nameparts[0] != "user" ||
|
||||
nameparts[1] != "mergerfs")
|
||||
if(strncmp("user.mergerfs.",attrname,sizeof("user.mergerfs.")-1))
|
||||
return -ENOATTR;
|
||||
|
||||
if(attrval.empty())
|
||||
return -EINVAL;
|
||||
|
||||
if(nameparts[2] == "srcmounts")
|
||||
if(!strcmp("srcmounts",attrbasename))
|
||||
return _setxattr_srcmounts(config.srcmounts,
|
||||
config.srcmountslock,
|
||||
config.destmount,
|
||||
attrval,
|
||||
flags);
|
||||
else if(!strncmp("category.",attrbasename,sizeof("category.")-1))
|
||||
return _setxattr_controlfile_category_policy(config,
|
||||
&attrbasename[sizeof("category.")-1],
|
||||
attrval,
|
||||
flags);
|
||||
else if(!strncmp("func.",attrbasename,sizeof("func.")-1))
|
||||
return _setxattr_controlfile_func_policy(config.policies,
|
||||
&attrbasename[sizeof("func.")-1],
|
||||
attrval,
|
||||
flags);
|
||||
|
||||
return _setxattr_policy(config.policies,
|
||||
nameparts[2],
|
||||
attrval,
|
||||
flags);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static
|
||||
|
@ -271,7 +303,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _setxattr(*config.search,
|
||||
return _setxattr(*config.setxattr,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
attrname,
|
||||
|
|
28
src/str.cpp
28
src/str.cpp
|
@ -26,6 +26,8 @@
|
|||
#include <vector>
|
||||
#include <sstream>
|
||||
|
||||
#include <fnmatch.h>
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using std::istringstream;
|
||||
|
@ -111,4 +113,30 @@ namespace str
|
|||
|
||||
return str::join(vec,idx,sep);
|
||||
}
|
||||
|
||||
void
|
||||
erase_fnmatches(const vector<string> &patterns,
|
||||
vector<string> &strs)
|
||||
{
|
||||
vector<string>::iterator si;
|
||||
vector<string>::const_iterator pi;
|
||||
|
||||
si = strs.begin();
|
||||
while(si != strs.end())
|
||||
{
|
||||
int match = FNM_NOMATCH;
|
||||
|
||||
for(pi = patterns.begin();
|
||||
pi != patterns.end() && match != 0;
|
||||
++pi)
|
||||
{
|
||||
match = fnmatch(pi->c_str(),si->c_str(),0);
|
||||
}
|
||||
|
||||
if(match == 0)
|
||||
si = strs.erase(si);
|
||||
else
|
||||
++si;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,4 +50,8 @@ namespace str
|
|||
std::string
|
||||
remove_common_prefix_and_join(const std::vector<std::string> &vec,
|
||||
const char sep);
|
||||
|
||||
void
|
||||
erase_fnmatches(const std::vector<std::string> &pattern,
|
||||
std::vector<std::string> &strs);
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _truncate(*config.search,
|
||||
return _truncate(*config.truncate,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
size);
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _unlink(*config.search,
|
||||
return _unlink(*config.unlink,
|
||||
config.srcmounts,
|
||||
fusepath);
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ namespace mergerfs
|
|||
const ugid::SetResetGuard ugid(fc->uid,fc->gid);
|
||||
const rwlock::ReadGuard readlock(&config.srcmountslock);
|
||||
|
||||
return _utimens(*config.search,
|
||||
return _utimens(*config.utimens,
|
||||
config.srcmounts,
|
||||
fusepath,
|
||||
ts);
|
||||
|
|
Loading…
Reference in New Issue
Block a user