Skip to content

Commit

Permalink
Merge pull request #1212 from trapexit/erofs
Browse files Browse the repository at this point in the history
Create functions can set branches RO on EROFS
  • Loading branch information
trapexit authored Jul 14, 2023
2 parents 36a4b7a + 707d298 commit 7a86ed6
Show file tree
Hide file tree
Showing 14 changed files with 230 additions and 56 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 10 additions & 3 deletions man/mergerfs.1
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions src/branches.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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_)
{
Expand Down
3 changes: 3 additions & 0 deletions src/branches.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class Branches final : public ToFromString
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;
Expand Down
9 changes: 4 additions & 5 deletions src/fs_cow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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_);
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);
Expand Down
75 changes: 75 additions & 0 deletions src/fs_is_rofs.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
ISC License
Copyright (c) 2023, Antonio SJ Musumeci <[email protected]>
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 changes: 40 additions & 27 deletions src/fs_mktemp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";

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_);
}
}
12 changes: 8 additions & 4 deletions src/fs_mktemp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 2 additions & 3 deletions src/fs_movefile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions src/fuse_create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/fuse_link.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
25 changes: 19 additions & 6 deletions src/fuse_mkdir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Loading

0 comments on commit 7a86ed6

Please sign in to comment.