From 707d298d7cc64796a9312bc2664832edd75201ae Mon Sep 17 00:00:00 2001 From: Antonio SJ Musumeci Date: Sun, 9 Jul 2023 22:42:47 -0500 Subject: [PATCH] Create functions can set branches RO on EROFS --- README.md | 6 ++++ man/mergerfs.1 | 13 ++++++-- src/branches.cpp | 20 ++++++++++++ src/branches.hpp | 3 ++ src/fs_cow.cpp | 9 +++--- src/fs_is_rofs.hpp | 75 ++++++++++++++++++++++++++++++++++++++++++++ src/fs_mktemp.cpp | 67 +++++++++++++++++++++++---------------- src/fs_mktemp.hpp | 12 ++++--- src/fs_movefile.cpp | 5 ++- src/fuse_create.cpp | 11 +++++++ src/fuse_link.cpp | 2 +- src/fuse_mkdir.cpp | 25 +++++++++++---- src/fuse_mknod.cpp | 28 ++++++++++++----- src/fuse_symlink.cpp | 10 ++++++ 14 files changed, 230 insertions(+), 56 deletions(-) create mode 100644 src/fs_is_rofs.hpp diff --git a/README.md b/README.md index de37f70b..5aca5ce2 100644 --- a/README.md +++ b/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 diff --git a/man/mergerfs.1 b/man/mergerfs.1 index ffed3fd2..aa25fa62 100644 --- a/man/mergerfs.1 +++ b/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. diff --git a/src/branches.cpp b/src/branches.cpp index d5eff6c2..083709b2 100644 --- a/src/branches.cpp +++ b/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 @@ -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_) { diff --git a/src/branches.hpp b/src/branches.hpp index 1988948a..a8ef781a 100644 --- a/src/branches.hpp +++ b/src/branches.hpp @@ -76,6 +76,9 @@ public: operator CPtr() const { std::lock_guard lg(_mutex); return _impl; } CPtr operator->() const { std::lock_guard lg(_mutex); return _impl; } +public: + void find_and_set_mode_ro(); + private: mutable std::mutex _mutex; Ptr _impl; diff --git a/src/fs_cow.cpp b/src/fs_cow.cpp index 37855329..b7b16436 100644 --- a/src/fs_cow.cpp +++ b/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); diff --git a/src/fs_is_rofs.hpp b/src/fs_is_rofs.hpp new file mode 100644 index 00000000..a666fcc0 --- /dev/null +++ b/src/fs_is_rofs.hpp @@ -0,0 +1,75 @@ +/* + ISC License + + Copyright (c) 2023, Antonio SJ Musumeci + + 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 + + +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_); + } +} diff --git a/src/fs_mktemp.cpp b/src/fs_mktemp.cpp index f705afcd..7bdd7c26 100644 --- a/src/fs_mktemp.cpp +++ b/src/fs_mktemp.cpp @@ -18,61 +18,74 @@ #include "errno.hpp" #include "fs_open.hpp" - -#include -#include +#include "fs_path.hpp" #include -using std::string; +#include +#include -#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 { - int - mktemp(string *base_, - const int flags_) + std::tuple + 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 + mktemp(std::string const filepath_, + int const flags_) + { + ghc::filesystem::path filepath{filepath_}; + + return fs::mktemp_in_dir(filepath.parent_path(),flags_); } } diff --git a/src/fs_mktemp.hpp b/src/fs_mktemp.hpp index aecf0e74..ebe8e5f1 100644 --- a/src/fs_mktemp.hpp +++ b/src/fs_mktemp.hpp @@ -19,11 +19,15 @@ #pragma once #include - +#include namespace fs { - int - mktemp(std::string *base, - const int flags); + std::tuple + mktemp(std::string const filepath, + int const flags); + + std::tuple + mktemp_in_dir(std::string const dirpath, + int const flags); } diff --git a/src/fs_movefile.cpp b/src/fs_movefile.cpp index 5c45ed64..86995491 100644 --- a/src/fs_movefile.cpp +++ b/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; diff --git a/src/fuse_create.cpp b/src/fuse_create.cpp index 11179118..a6b15e18 100644 --- a/src/fuse_create.cpp +++ b/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; } diff --git a/src/fuse_link.cpp b/src/fuse_link.cpp index 754228ce..ea62dc6c 100644 --- a/src/fuse_link.cpp +++ b/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; } diff --git a/src/fuse_mkdir.cpp b/src/fuse_mkdir.cpp index c8bc3e57..88a9fa9d 100644 --- a/src/fuse_mkdir.cpp +++ b/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, - 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; } } diff --git a/src/fuse_mknod.cpp b/src/fuse_mknod.cpp index ad7b4f46..34a4ae1f 100644 --- a/src/fuse_mknod.cpp +++ b/src/fuse_mknod.cpp @@ -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; } } diff --git a/src/fuse_symlink.cpp b/src/fuse_symlink.cpp index e413f6b6..fcc9ef36 100644 --- a/src/fuse_symlink.cpp +++ b/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) {