move on enospc when writing feature. closes #141

This feature mimics the standard mhddfs behavior but is more thorough.
If a write fails and the errno is set to ENOSPC then mergerfs will (if
the feature is enabled) attempt to move the file to the drive with
the most free space but only if it has enough room for the file plus
the amount to be written. If that transfer is successful it will then
unlink the original file and attempt the previously failed write again.

The copy includes copying the path and file including the acls, owners,
attributes, extended attributes, and timestamps.
This commit is contained in:
Antonio SJ Musumeci
2015-09-25 15:14:17 -04:00
parent 4e5578de9c
commit 5808ab7795
66 changed files with 1837 additions and 665 deletions
+71 -421
View File
@@ -24,128 +24,28 @@
#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <cstdlib>
#include <iterator>
#include <stdlib.h>
#include <limits.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <glob.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <sys/types.h>
#include <unistd.h>
#include <glob.h>
#include "fs.hpp"
#include "xattr.hpp"
#include "fs_attr.hpp"
#include "fs_path.hpp"
#include "fs_xattr.hpp"
#include "str.hpp"
using std::string;
using std::vector;
using std::map;
using std::istringstream;
template <typename Iter>
Iter
random_element(Iter begin,
Iter end)
{
const unsigned long n = std::distance(begin, end);
std::advance(begin, (std::rand() % n));
return begin;
}
namespace fs
{
namespace path
{
string
dirname(const string &path)
{
string parent = path;
string::reverse_iterator i;
string::reverse_iterator bi;
bi = parent.rend();
i = parent.rbegin();
while(*i == '/' && i != bi)
i++;
while(*i != '/' && i != bi)
i++;
while(*i == '/' && i != bi)
i++;
parent.erase(i.base(),parent.end());
return parent;
}
string
basename(const string &path)
{
return path.substr(path.find_last_of('/')+1);
}
bool
is_empty(const string &path)
{
DIR *dir;
struct dirent *de;
dir = ::opendir(path.c_str());
if(!dir)
return false;
while((de = ::readdir(dir)))
{
const char *d_name = de->d_name;
if(d_name[0] == '.' &&
((d_name[1] == '\0') ||
(d_name[1] == '.' && d_name[2] == '\0')))
continue;
::closedir(dir);
return false;
}
::closedir(dir);
return true;
}
bool
exists(const vector<string> &paths,
const string &fusepath)
{
for(size_t i = 0, ei = paths.size(); i != ei; i++)
{
int rv;
string path;
struct stat st;
fs::path::make(paths[i],fusepath,path);
rv = ::lstat(path.c_str(),&st);
if(rv == 0)
return true;
}
return false;
}
}
void
findallfiles(const vector<string> &srcmounts,
const string &fusepath,
@@ -166,331 +66,37 @@ namespace fs
}
int
listxattr(const string &path,
vector<char> &attrs)
{
#ifndef WITHOUT_XATTR
int rv;
int size;
rv = -1;
errno = ERANGE;
while(rv == -1 && errno == ERANGE)
{
size = ::listxattr(path.c_str(),NULL,0);
attrs.resize(size);
rv = ::listxattr(path.c_str(),&attrs[0],size);
}
return rv;
#else
errno = ENOTSUP;
return -1;
#endif
}
int
listxattr(const string &path,
vector<string> &attrvector)
findonfs(const vector<string> &srcmounts,
const string &fusepath,
const int fd,
string &basepath)
{
int rv;
vector<char> attrs;
string tmppath;
unsigned long fsid;
struct statvfs buf;
rv = listxattr(path,attrs);
if(rv != -1)
{
string tmp(attrs.begin(),attrs.end());
str::split(attrvector,tmp,'\0');
}
return rv;
}
int
listxattr(const string &path,
string &attrstr)
{
int rv;
vector<char> attrs;
rv = listxattr(path,attrs);
if(rv != -1)
attrstr = string(attrs.begin(),attrs.end());
return rv;
}
int
getxattr(const string &path,
const string &attr,
vector<char> &value)
{
#ifndef WITHOUT_XATTR
int rv;
int size;
rv = -1;
errno = ERANGE;
while(rv == -1 && errno == ERANGE)
{
size = ::getxattr(path.c_str(),attr.c_str(),NULL,0);
value.resize(size);
rv = ::getxattr(path.c_str(),attr.c_str(),&value[0],size);
}
return rv;
#else
errno = ENOTSUP;
return -1;
#endif
}
int
getxattr(const string &path,
const string &attr,
string &value)
{
int rv;
vector<char> tmpvalue;
rv = getxattr(path,attr,tmpvalue);
if(rv != -1)
value = string(tmpvalue.begin(),tmpvalue.end());
return rv;
}
int
getxattrs(const string &path,
map<string,string> &attrs)
{
int rv;
string attrstr;
rv = listxattr(path,attrstr);
rv = ::fstatvfs(fd,&buf);
if(rv == -1)
return -1;
{
string key;
istringstream ss(attrstr);
while(getline(ss,key,'\0'))
{
string value;
rv = getxattr(path,key,value);
if(rv != -1)
attrs[key] = value;
}
}
return 0;
}
int
setxattr(const string &path,
const string &key,
const string &value,
const int flags)
{
#ifndef WITHOUT_XATTR
return ::setxattr(path.c_str(),
key.c_str(),
value.data(),
value.size(),
flags);
#else
errno = ENOTSUP;
return -1;
#endif
}
int
setxattr(const int fd,
const string &key,
const string &value,
const int flags)
{
#ifndef WITHOUT_XATTR
return ::fsetxattr(fd,
key.c_str(),
value.data(),
value.size(),
flags);
#else
errno = ENOTSUP;
return -1;
#endif
}
int
setxattrs(const string &path,
const map<string,string> &attrs)
{
int fd;
fd = ::open(path.c_str(),O_RDONLY|O_NONBLOCK);
if(fd == -1)
return -1;
for(map<string,string>::const_iterator
i = attrs.begin(), ei = attrs.end(); i != ei; ++i)
fsid = buf.f_fsid;
for(int i = 0, ei = srcmounts.size(); i != ei; i++)
{
setxattr(fd,i->first,i->second,0);
}
fs::path::make(srcmounts[i],fusepath,tmppath);
return ::close(fd);
}
int
copyxattrs(const string &from,
const string &to)
{
int rv;
map<string,string> attrs;
rv = getxattrs(from,attrs);
if(rv == -1)
return -1;
return setxattrs(to,attrs);
}
static
int
get_fs_ioc_flags(const string &file,
int &flags)
{
int fd;
int rv;
const int openflags = O_RDONLY|O_NONBLOCK;
fd = ::open(file.c_str(),openflags);
if(fd == -1)
return -1;
rv = ::ioctl(fd,FS_IOC_GETFLAGS,&flags);
if(rv == -1)
{
int error = errno;
::close(fd);
errno = error;
return -1;
}
return ::close(fd);
}
static
int
set_fs_ioc_flags(const string &file,
const int flags)
{
int fd;
int rv;
const int openflags = O_RDONLY|O_NONBLOCK;
fd = ::open(file.c_str(),openflags);
if(fd == -1)
return -1;
rv = ::ioctl(fd,FS_IOC_SETFLAGS,&flags);
if(rv == -1)
{
int error = errno;
::close(fd);
errno = error;
return -1;
}
return ::close(fd);
}
int
copyattr(const string &from,
const string &to)
{
int rv;
int flags;
rv = get_fs_ioc_flags(from,flags);
if(rv == -1)
return -1;
return set_fs_ioc_flags(to,flags);
}
static
bool
ignorable_error(const int err)
{
switch(err)
{
case ENOTTY:
case ENOTSUP:
#if ENOTSUP != EOPNOTSUPP
case EOPNOTSUPP:
#endif
return true;
}
return false;
}
int
clonepath(const string &fromsrc,
const string &tosrc,
const string &relative)
{
int rv;
struct stat st;
string topath;
string frompath;
string dirname;
dirname = fs::path::dirname(relative);
if(!dirname.empty())
{
rv = clonepath(fromsrc,tosrc,dirname);
rv = ::statvfs(tmppath.c_str(),&buf);
if(rv == -1)
return -1;
continue;
if(buf.f_fsid == fsid)
{
basepath = srcmounts[i];
return 0;
}
}
fs::path::make(fromsrc,relative,frompath);
rv = ::stat(frompath.c_str(),&st);
if(rv == -1)
return -1;
else if(!S_ISDIR(st.st_mode))
return (errno = ENOTDIR,-1);
fs::path::make(tosrc,relative,topath);
rv = ::mkdir(topath.c_str(),st.st_mode);
if(rv == -1)
{
if(errno != EEXIST)
return -1;
rv = ::chmod(topath.c_str(),st.st_mode);
if(rv == -1)
return -1;
}
rv = ::chown(topath.c_str(),st.st_uid,st.st_gid);
if(rv == -1)
return -1;
// It may not support it... it's fine...
rv = copyattr(frompath,topath);
if(rv == -1 && !ignorable_error(errno))
return -1;
rv = copyxattrs(frompath,topath);
if(rv == -1 && !ignorable_error(errno))
return -1;
return 0;
return (errno=ENOENT,-1);
}
void
@@ -528,4 +134,48 @@ namespace fs
strs[i] = buf;
}
}
int
getfl(const int fd)
{
return ::fcntl(fd,F_GETFL,0);
}
int
mfs(const vector<string> &basepaths,
const size_t minfreespace,
string &path)
{
fsblkcnt_t mfs;
ssize_t mfsidx;
mfs = 0;
mfsidx = -1;
for(size_t i = 0, ei = basepaths.size(); i != ei; i++)
{
int rv;
struct statvfs fsstats;
const string &basepath = basepaths[i];
rv = ::statvfs(basepath.c_str(),&fsstats);
if(rv == 0)
{
fsblkcnt_t spaceavail;
spaceavail = (fsstats.f_frsize * fsstats.f_bavail);
if((spaceavail > mfs) && (spaceavail >= minfreespace))
{
mfs = spaceavail;
mfsidx = i;
}
}
}
if(mfsidx == -1)
return (errno=ENOENT,-1);
path = basepaths[mfsidx];
return 0;
}
};