Browse Source

Create functions can set branches RO on EROFS

pull/1212/head
Antonio SJ Musumeci 1 year ago
parent
commit
707d298d7c
  1. 6
      README.md
  2. 13
      man/mergerfs.1
  3. 20
      src/branches.cpp
  4. 3
      src/branches.hpp
  5. 9
      src/fs_cow.cpp
  6. 75
      src/fs_is_rofs.hpp
  7. 65
      src/fs_mktemp.cpp
  8. 12
      src/fs_mktemp.hpp
  9. 5
      src/fs_movefile.cpp
  10. 11
      src/fuse_create.cpp
  11. 2
      src/fuse_link.cpp
  12. 15
      src/fuse_mkdir.cpp
  13. 16
      src/fuse_mknod.cpp
  14. 10
      src/fuse_symlink.cpp

6
README.md

@ -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

13
man/mergerfs.1

@ -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.

20
src/branches.cpp

@ -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_)
{

3
src/branches.hpp

@ -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;

9
src/fs_cow.cpp

@ -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

@ -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_);
}
}

65
src/fs_mktemp.cpp

@ -18,61 +18,74 @@
#include "errno.hpp"
#include "fs_open.hpp"
#include <cstdlib>
#include <string>
#include "fs_path.hpp"
#include <limits.h>
using std::string;
#include <cstdlib>
#include <string>
#define PADLEN 6
#define MAX_ATTEMPTS 10
#define PAD_LEN 16
#define MAX_ATTEMPTS 3
static char const CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
namespace l
{
static
string
generate_tmp_path(const string &base_)
std::string
generate_tmp_path(std::string const base_)
{
string tmp;
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,tmp_filepath};
}
return fd;
return {-EEXIST,std::string{}};
}
return (errno=EEXIST,-1);
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_);
}
}

12
src/fs_mktemp.hpp

@ -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);
}

5
src/fs_movefile.cpp

@ -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;

11
src/fuse_create.cpp

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

2
src/fuse_link.cpp

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

15
src/fuse_mkdir.cpp

@ -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,
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;
}
}

16
src/fuse_mknod.cpp

@ -152,11 +152,22 @@ 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,
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_,
@ -164,4 +175,7 @@ namespace FUSE
fc->umask,
rdev_);
}
return rv;
}
}

10
src/fuse_symlink.cpp

@ -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)
{

Loading…
Cancel
Save