mirror of
https://github.com/trapexit/mergerfs.git
synced 2024-04-21 12:31:43 +00:00
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:
+71
-421
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user