Create functions can set branches RO on EROFS

This commit is contained in:
Antonio SJ Musumeci 2023-07-09 22:42:47 -05:00
parent 36a4b7a23a
commit 707d298d7c
14 changed files with 230 additions and 56 deletions

View File

@ -747,6 +747,12 @@ If all branches are filtered an error will be returned. Typically
device) depending on the most recent reason for filtering a
branch. **ENOENT** will be returned if no eligible branch is found.
If **create**, **mkdir**, **mknod**, or **symlink** fail with `EROFS`
or other fundimental errors then mergerfs will mark any branch found
to be read-only as such (IE will set the mode `RO`) and will rerun the
policy and try again. This is mostly for `ext4` filesystems that can
suddenly become read-only when it encounters an error.
#### Path Preservation

View File

@ -311,12 +311,12 @@ reading FUSE messages which are dispatched to process threads.
-1 means disabled otherwise acts like \f[C]read-thread-count\f[R].
(default: -1)
.IP \[bu] 2
\f[B]process-thread-queue-depth=INT\f[R]: Sets the number of requests
\f[B]process-thread-queue-depth=UINT\f[R]: Sets the number of requests
any single process thread can have queued up at one time.
Meaning the total memory usage of the queues is queue depth multiplied
by the number of process threads plus read thread count.
-1 sets the depth to the same as the process thread count.
(default: -1)
0 sets the depth to the same as the process thread count.
(default: 0)
.IP \[bu] 2
\f[B]pin-threads=STR\f[R]: Selects a strategy to pin threads to CPUs
(default: unset)
@ -937,6 +937,13 @@ Typically \f[B]EROFS\f[R] (read-only filesystem) or \f[B]ENOSPC\f[R] (no
space left on device) depending on the most recent reason for filtering
a branch.
\f[B]ENOENT\f[R] will be returned if no eligible branch is found.
.PP
If \f[B]create\f[R], \f[B]mkdir\f[R], \f[B]mknod\f[R], or
\f[B]symlink\f[R] fail with \f[C]EROFS\f[R] or other fundimental errors
then mergerfs will mark any branch found to be read-only as such (IE
will set the mode \f[C]RO\f[R]) and will rerun the policy and try again.
This is mostly for \f[C]ext4\f[R] filesystems that can suddenly become
read-only when it encounters an error.
.SS Path Preservation
.PP
Policies, as described below, are of two basic classifications.

View File

@ -21,10 +21,12 @@
#include "errno.hpp"
#include "from_string.hpp"
#include "fs_glob.hpp"
#include "fs_is_rofs.hpp"
#include "fs_realpathize.hpp"
#include "nonstd/optional.hpp"
#include "num.hpp"
#include "str.hpp"
#include "syslog.hpp"
#include <string>
@ -415,6 +417,24 @@ Branches::to_string(void) const
return _impl->to_string();
}
void
Branches::find_and_set_mode_ro()
{
for(auto &branch : *_impl)
{
if(branch.mode != Branch::Mode::RW)
continue;
if(!fs::is_rofs_but_not_mounted_ro(branch.path))
continue;
syslog_warning("Branch %s found to be readonly - setting its mode to RO",
branch.path.c_str());
branch.mode = Branch::Mode::RO;
}
}
SrcMounts::SrcMounts(Branches &b_)
: _branches(b_)
{

View File

@ -76,6 +76,9 @@ public:
operator CPtr() const { std::lock_guard<std::mutex> lg(_mutex); return _impl; }
CPtr operator->() const { std::lock_guard<std::mutex> lg(_mutex); return _impl; }
public:
void find_and_set_mode_ro();
private:
mutable std::mutex _mutex;
Ptr _impl;

View File

@ -22,6 +22,7 @@
#include "fs_lstat.hpp"
#include "fs_mktemp.hpp"
#include "fs_open.hpp"
#include "fs_path.hpp"
#include "fs_rename.hpp"
#include "fs_unlink.hpp"
@ -109,16 +110,14 @@ namespace fs
int rv;
int src_fd;
int dst_fd;
string dst_fullpath;
std::string dst_fullpath;
src_fd = fs::open(src_fullpath_,O_RDONLY|O_NOFOLLOW);
if(src_fd == -1)
return -1;
dst_fullpath = src_fullpath_;
dst_fd = fs::mktemp(&dst_fullpath,O_WRONLY);
if(dst_fd == -1)
std::tie(dst_fd,dst_fullpath) = fs::mktemp(src_fullpath_,O_WRONLY);
if(dst_fd < 0)
return l::cleanup_on_error(src_fd);
rv = fs::clonefile(src_fd,dst_fd);

75
src/fs_is_rofs.hpp Normal file
View File

@ -0,0 +1,75 @@
/*
ISC License
Copyright (c) 2023, 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
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include "fs_mktemp.hpp"
#include "fs_statvfs.hpp"
#include "fs_unlink.hpp"
#include "statvfs_util.hpp"
#include "ugid.hpp"
#include <string>
namespace fs
{
static
inline
bool
is_mounted_rofs(const std::string path_)
{
int rv;
struct statvfs st;
rv = fs::statvfs(path_,&st);
return ((rv == 0) ? StatVFS::readonly(st) : false);
}
static
inline
bool
is_rofs(std::string path_)
{
ugid::SetRootGuard const ugid;
int fd;
std::string tmp_filepath;
std::tie(fd,tmp_filepath) = fs::mktemp_in_dir(path_,O_WRONLY);
if(fd < 0)
return (fd == -EROFS);
fs::close(fd);
fs::unlink(tmp_filepath);
return false;
}
static
inline
bool
is_rofs_but_not_mounted_ro(const std::string path_)
{
if(fs::is_mounted_rofs(path_))
return false;
return fs::is_rofs(path_);
}
}

View File

@ -18,61 +18,74 @@
#include "errno.hpp"
#include "fs_open.hpp"
#include "fs_path.hpp"
#include <limits.h>
#include <cstdlib>
#include <string>
#include <limits.h>
#define PAD_LEN 16
#define MAX_ATTEMPTS 3
using std::string;
static char const CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
#define PADLEN 6
#define MAX_ATTEMPTS 10
static
string
generate_tmp_path(const string &base_)
namespace l
{
string tmp;
static
std::string
generate_tmp_path(std::string const base_)
{
fs::Path path;
std::string filename;
tmp = base_;
if((tmp.size() + PADLEN + 1) > PATH_MAX)
tmp.resize(tmp.size() - PADLEN - 1);
tmp += '.';
for(int i = 0; i < PADLEN; i++)
tmp += ('A' + (std::rand() % 26));
filename = '.';
for(int i = 0; i < PAD_LEN; i++)
filename += CHARS[std::rand() % (sizeof(CHARS) - 1)];
return tmp;
path = base_;
path /= filename;
return path.string();
}
}
namespace fs
{
int
mktemp(string *base_,
const int flags_)
std::tuple<int,std::string>
mktemp_in_dir(std::string const dirpath_,
int const flags_)
{
int fd;
int count;
int flags;
string tmppath;
std::string tmp_filepath;
fd = -1;
count = MAX_ATTEMPTS;
flags = (flags_ | O_EXCL | O_CREAT | O_TRUNC);
while(count-- > 0)
{
tmppath = generate_tmp_path(*base_);
tmp_filepath = l::generate_tmp_path(dirpath_);
fd = fs::open(tmppath,flags,S_IWUSR);
fd = fs::open(tmp_filepath,flags,S_IWUSR);
if((fd == -1) && (errno == EEXIST))
continue;
else if(fd != -1)
*base_ = tmppath;
if(fd == -1)
return {-errno,std::string{}};
return fd;
return {fd,tmp_filepath};
}
return (errno=EEXIST,-1);
return {-EEXIST,std::string{}};
}
std::tuple<int,std::string>
mktemp(std::string const filepath_,
int const flags_)
{
ghc::filesystem::path filepath{filepath_};
return fs::mktemp_in_dir(filepath.parent_path(),flags_);
}
}

View File

@ -19,11 +19,15 @@
#pragma once
#include <string>
#include <tuple>
namespace fs
{
int
mktemp(std::string *base,
const int flags);
std::tuple<int,std::string>
mktemp(std::string const filepath,
int const flags);
std::tuple<int,std::string>
mktemp_in_dir(std::string const dirpath,
int const flags);
}

View File

@ -117,9 +117,8 @@ namespace l
dstfd_filepath = dstfd_branch[0];
fs::path::append(dstfd_filepath,fusepath_);
dstfd_tmp_filepath = dstfd_filepath;
dstfd = fs::mktemp(&dstfd_tmp_filepath,O_WRONLY);
if(dstfd == -1)
std::tie(dstfd,dstfd_tmp_filepath) = fs::mktemp(dstfd_filepath,O_WRONLY);
if(dstfd < 0)
{
fs::close(srcfd);
return -ENOSPC;

View File

@ -207,6 +207,17 @@ namespace FUSE
ffi_,
mode_,
fc->umask);
if(rv == -EROFS)
{
Config::Write()->branches.find_and_set_mode_ro();
rv = l::create(cfg->func.getattr.policy,
cfg->func.create.policy,
cfg->branches,
fusepath_,
ffi_,
mode_,
fc->umask);
}
return rv;
}

View File

@ -352,7 +352,7 @@ namespace FUSE
rv = l::link(cfg,oldpath_,newpath_,st_,timeouts_);
if(rv == -EXDEV)
return l::link_exdev(cfg,oldpath_,newpath_,st_,timeouts_);
rv = l::link_exdev(cfg,oldpath_,newpath_,st_,timeouts_);
return rv;
}

View File

@ -150,15 +150,28 @@ namespace FUSE
mkdir(const char *fusepath_,
mode_t mode_)
{
int rv;
Config::Read cfg;
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return l::mkdir(cfg->func.getattr.policy,
cfg->func.mkdir.policy,
cfg->branches,
fusepath_,
mode_,
fc->umask);
rv = l::mkdir(cfg->func.getattr.policy,
cfg->func.mkdir.policy,
cfg->branches,
fusepath_,
mode_,
fc->umask);
if(rv == -EROFS)
{
Config::Write()->branches.find_and_set_mode_ro();
rv = l::mkdir(cfg->func.getattr.policy,
cfg->func.mkdir.policy,
cfg->branches,
fusepath_,
mode_,
fc->umask);
}
return rv;
}
}

View File

@ -152,16 +152,30 @@ namespace FUSE
mode_t mode_,
dev_t rdev_)
{
int rv;
Config::Read cfg;
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
return l::mknod(cfg->func.getattr.policy,
cfg->func.mknod.policy,
cfg->branches,
fusepath_,
mode_,
fc->umask,
rdev_);
rv = l::mknod(cfg->func.getattr.policy,
cfg->func.mknod.policy,
cfg->branches,
fusepath_,
mode_,
fc->umask,
rdev_);
if(rv == -EROFS)
{
Config::Write()->branches.find_and_set_mode_ro();
rv = l::mknod(cfg->func.getattr.policy,
cfg->func.mknod.policy,
cfg->branches,
fusepath_,
mode_,
fc->umask,
rdev_);
}
return rv;
}
}

View File

@ -157,6 +157,16 @@ namespace FUSE
target_,
linkpath_,
st_);
if(rv == -EROFS)
{
Config::Write()->branches.find_and_set_mode_ro();
rv = l::symlink(cfg->func.getattr.policy,
cfg->func.symlink.policy,
cfg->branches,
target_,
linkpath_,
st_);
}
if(timeouts_ != NULL)
{