Browse Source

Merge pull request #1212 from trapexit/erofs

Create functions can set branches RO on EROFS
pull/1226/head 2.36.0
trapexit 1 year ago
committed by GitHub
parent
commit
7a86ed6508
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  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. 67
      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. 25
      src/fuse_mkdir.cpp
  13. 28
      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 device) depending on the most recent reason for filtering a
branch. **ENOENT** will be returned if no eligible branch is found. 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 #### 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]. -1 means disabled otherwise acts like \f[C]read-thread-count\f[R].
(default: -1) (default: -1)
.IP \[bu] 2 .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. any single process thread can have queued up at one time.
Meaning the total memory usage of the queues is queue depth multiplied Meaning the total memory usage of the queues is queue depth multiplied
by the number of process threads plus read thread count. 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 .IP \[bu] 2
\f[B]pin-threads=STR\f[R]: Selects a strategy to pin threads to CPUs \f[B]pin-threads=STR\f[R]: Selects a strategy to pin threads to CPUs
(default: unset) (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 space left on device) depending on the most recent reason for filtering
a branch. a branch.
\f[B]ENOENT\f[R] will be returned if no eligible branch is found. \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 .SS Path Preservation
.PP .PP
Policies, as described below, are of two basic classifications. Policies, as described below, are of two basic classifications.

20
src/branches.cpp

@ -21,10 +21,12 @@
#include "errno.hpp" #include "errno.hpp"
#include "from_string.hpp" #include "from_string.hpp"
#include "fs_glob.hpp" #include "fs_glob.hpp"
#include "fs_is_rofs.hpp"
#include "fs_realpathize.hpp" #include "fs_realpathize.hpp"
#include "nonstd/optional.hpp" #include "nonstd/optional.hpp"
#include "num.hpp" #include "num.hpp"
#include "str.hpp" #include "str.hpp"
#include "syslog.hpp"
#include <string> #include <string>
@ -415,6 +417,24 @@ Branches::to_string(void) const
return _impl->to_string(); 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_) SrcMounts::SrcMounts(Branches &b_)
: _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; } operator CPtr() const { std::lock_guard<std::mutex> lg(_mutex); return _impl; }
CPtr operator->() 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: private:
mutable std::mutex _mutex; mutable std::mutex _mutex;
Ptr _impl; Ptr _impl;

9
src/fs_cow.cpp

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

67
src/fs_mktemp.cpp

@ -18,61 +18,74 @@
#include "errno.hpp" #include "errno.hpp"
#include "fs_open.hpp" #include "fs_open.hpp"
#include <cstdlib>
#include <string>
#include "fs_path.hpp"
#include <limits.h> #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";
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 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 fd;
int count; int count;
int flags; int flags;
string tmppath;
std::string tmp_filepath;
fd = -1; fd = -1;
count = MAX_ATTEMPTS; count = MAX_ATTEMPTS;
flags = (flags_ | O_EXCL | O_CREAT | O_TRUNC); flags = (flags_ | O_EXCL | O_CREAT | O_TRUNC);
while(count-- > 0) 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)) if((fd == -1) && (errno == EEXIST))
continue; 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_);
} }
} }

12
src/fs_mktemp.hpp

@ -19,11 +19,15 @@
#pragma once #pragma once
#include <string> #include <string>
#include <tuple>
namespace fs 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]; dstfd_filepath = dstfd_branch[0];
fs::path::append(dstfd_filepath,fusepath_); 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); fs::close(srcfd);
return -ENOSPC; return -ENOSPC;

11
src/fuse_create.cpp

@ -207,6 +207,17 @@ namespace FUSE
ffi_, ffi_,
mode_, mode_,
fc->umask); 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; return rv;
} }

2
src/fuse_link.cpp

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

25
src/fuse_mkdir.cpp

@ -150,15 +150,28 @@ namespace FUSE
mkdir(const char *fusepath_, mkdir(const char *fusepath_,
mode_t mode_) mode_t mode_)
{ {
int rv;
Config::Read cfg; Config::Read cfg;
const fuse_context *fc = fuse_get_context(); const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid); 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;
} }
} }

28
src/fuse_mknod.cpp

@ -152,16 +152,30 @@ namespace FUSE
mode_t mode_, mode_t mode_,
dev_t rdev_) dev_t rdev_)
{ {
int rv;
Config::Read cfg; Config::Read cfg;
const fuse_context *fc = fuse_get_context(); const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid); 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;
} }
} }

10
src/fuse_symlink.cpp

@ -157,6 +157,16 @@ namespace FUSE
target_, target_,
linkpath_, linkpath_,
st_); 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) if(timeouts_ != NULL)
{ {

Loading…
Cancel
Save