mirror of https://github.com/trapexit/mergerfs.git
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 12156 additions and 43 deletions
-
1DEPENDENCIES
-
19Makefile
-
2buildtools/install-build-pkgs
-
1debian/compat
-
2debian/control
-
6debian/rules
-
2libfuse/Makefile
-
1mergerfs.spec
-
11527src/CLI11.hpp
-
1src/fs_clonefile.cpp
-
2src/fs_copydata_copy_file_range.cpp
-
4src/fs_copydata_readwrite.cpp
-
39src/fs_copyfile.cpp
-
9src/fs_copyfile.hpp
-
2src/fs_is_rofs.hpp
-
37src/fs_mktemp.cpp
-
8src/fs_mktemp.hpp
-
454src/fsck_mergerfs.cpp
-
6src/fsck_mergerfs.hpp
-
14src/fuse_ioctl.cpp
-
19src/mergerfs.cpp
-
15src/mergerfs_ioctl.hpp
-
17src/str.cpp
-
5src/str.hpp
@ -1 +0,0 @@ |
|||||
9 |
|
@ -1,11 +1,13 @@ |
|||||
#!/usr/bin/make -f |
#!/usr/bin/make -f |
||||
# -*- makefile -*- |
# -*- makefile -*- |
||||
|
|
||||
# Uncomment this to turn on verbose mode. |
|
||||
export DH_VERBOSE=1 |
export DH_VERBOSE=1 |
||||
|
|
||||
%: |
%: |
||||
dh $@ --parallel |
|
||||
|
dh $@ --parallel --max-parallel=6 |
||||
|
|
||||
|
override_dh_strip: |
||||
|
dh_strip --no-automatic-dbgsym |
||||
|
|
||||
override_dh_auto_install: |
override_dh_auto_install: |
||||
$(MAKE) DESTDIR=$(CURDIR)/debian/mergerfs PREFIX=/usr install |
$(MAKE) DESTDIR=$(CURDIR)/debian/mergerfs PREFIX=/usr install |
11527
src/CLI11.hpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,39 @@ |
|||||
|
#include "fs_copyfile.hpp"
|
||||
|
|
||||
|
#include "fs_clonefile.hpp"
|
||||
|
#include "fs_close.hpp"
|
||||
|
#include "fs_mktemp.hpp"
|
||||
|
#include "fs_open.hpp"
|
||||
|
#include "fs_rename.hpp"
|
||||
|
#include "scope_guard.hpp"
|
||||
|
|
||||
|
#include <fcntl.h>
|
||||
|
|
||||
|
|
||||
|
int |
||||
|
fs::copyfile(const std::filesystem::path &src_, |
||||
|
const std::filesystem::path &dst_) |
||||
|
{ |
||||
|
int rv; |
||||
|
int src_fd; |
||||
|
int dst_fd; |
||||
|
std::string dst_tmppath; |
||||
|
|
||||
|
src_fd = fs::open(src_,O_RDONLY|O_NOFOLLOW); |
||||
|
if(src_fd < 0) |
||||
|
return src_fd; |
||||
|
DEFER { fs::close(src_fd); }; |
||||
|
|
||||
|
std::tie(dst_fd,dst_tmppath) = fs::mktemp(dst_,O_RDWR); |
||||
|
if(dst_fd < 0) |
||||
|
return dst_fd; |
||||
|
DEFER { fs::close(dst_fd); }; |
||||
|
|
||||
|
rv = fs::clonefile(src_fd,dst_fd); |
||||
|
if(rv < 0) |
||||
|
return rv; |
||||
|
|
||||
|
rv = fs::rename(dst_tmppath,dst_); |
||||
|
|
||||
|
return rv; |
||||
|
} |
@ -0,0 +1,9 @@ |
|||||
|
#pragma once
|
||||
|
|
||||
|
#include <filesystem>
|
||||
|
|
||||
|
namespace fs |
||||
|
{ |
||||
|
int copyfile(const std::filesystem::path &src, |
||||
|
const std::filesystem::path &dst); |
||||
|
} |
@ -0,0 +1,454 @@ |
|||||
|
#include "fsck_mergerfs.hpp"
|
||||
|
|
||||
|
#include "fs_lchmod.hpp"
|
||||
|
#include "fs_lchown.hpp"
|
||||
|
#include "fs_close.hpp"
|
||||
|
#include "fs_ioctl.hpp"
|
||||
|
#include "fs_lgetxattr.hpp"
|
||||
|
#include "fs_lstat.hpp"
|
||||
|
#include "fs_open.hpp"
|
||||
|
#include "int_types.h"
|
||||
|
#include "mergerfs_ioctl.hpp"
|
||||
|
#include "str.hpp"
|
||||
|
#include "fs_copyfile.hpp"
|
||||
|
|
||||
|
#include "fmt/core.h"
|
||||
|
#include "fmt/chrono.h"
|
||||
|
#include "CLI11.hpp"
|
||||
|
|
||||
|
#include <filesystem>
|
||||
|
|
||||
|
namespace FS = std::filesystem; |
||||
|
|
||||
|
struct PathStat |
||||
|
{ |
||||
|
PathStat(const std::string &path_) |
||||
|
: path(path_), |
||||
|
st{} |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
std::string path; |
||||
|
struct stat st; |
||||
|
}; |
||||
|
|
||||
|
using PathStatVec = std::vector<PathStat>; |
||||
|
using FixFunc = std::function<void(const PathStatVec&,const bool)>; |
||||
|
|
||||
|
static |
||||
|
bool |
||||
|
_files_differ(const PathStatVec &paths_, |
||||
|
const bool check_size_) |
||||
|
{ |
||||
|
if(paths_.size() <= 1) |
||||
|
return false; |
||||
|
|
||||
|
const struct stat &st0 = paths_[0].st; |
||||
|
for(u64 i = 1; i < paths_.size(); i++) |
||||
|
{ |
||||
|
const struct stat &st = paths_[i].st; |
||||
|
|
||||
|
if(st.st_mode != st0.st_mode) |
||||
|
return true; |
||||
|
if(st.st_uid != st0.st_uid) |
||||
|
return true; |
||||
|
if(st.st_gid != st0.st_gid) |
||||
|
return true; |
||||
|
if(check_size_ && |
||||
|
S_ISREG(st0.st_mode) && |
||||
|
(st.st_size != st0.st_size)) |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
bool |
||||
|
_files_same_type(const PathStatVec &paths_) |
||||
|
{ |
||||
|
mode_t type; |
||||
|
|
||||
|
if(paths_.size() <= 1) |
||||
|
return true; |
||||
|
|
||||
|
type = (paths_[0].st.st_mode & S_IFMT); |
||||
|
for(u64 i = 1; i < paths_.size(); i++) |
||||
|
{ |
||||
|
if((paths_[i].st.st_mode & S_IFMT) != type) |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
const char* |
||||
|
_file_type(const mode_t mode_) |
||||
|
{ |
||||
|
switch(mode_ & S_IFMT) |
||||
|
{ |
||||
|
case S_IFBLK: |
||||
|
return "block dev"; |
||||
|
case S_IFCHR: |
||||
|
return "char dev"; |
||||
|
case S_IFDIR: |
||||
|
return "directory"; |
||||
|
case S_IFIFO: |
||||
|
return "fifo"; |
||||
|
case S_IFLNK: |
||||
|
return "symlink"; |
||||
|
case S_IFSOCK: |
||||
|
return "socket"; |
||||
|
case S_IFREG: |
||||
|
return "regular"; |
||||
|
} |
||||
|
|
||||
|
return "unknown"; |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
void |
||||
|
_compare_files(const std::string &mergerfs_path_, |
||||
|
PathStatVec &pathstats_, |
||||
|
const FixFunc fix_func_, |
||||
|
const bool check_size_, |
||||
|
const bool copy_file_) |
||||
|
{ |
||||
|
int rv; |
||||
|
|
||||
|
if(pathstats_.size() <= 1) |
||||
|
return; |
||||
|
|
||||
|
for(auto &pathstat : pathstats_) |
||||
|
{ |
||||
|
rv = fs::lstat(pathstat.path,&pathstat.st); |
||||
|
if(rv == -1) |
||||
|
pathstat.st.st_size = -errno; |
||||
|
} |
||||
|
|
||||
|
if(!::_files_differ(pathstats_,check_size_)) |
||||
|
return; |
||||
|
|
||||
|
fmt::println("* {}",mergerfs_path_); |
||||
|
for(u64 i = 0; i < pathstats_.size(); i++) |
||||
|
{ |
||||
|
const auto &path = pathstats_[i].path; |
||||
|
const auto &st = pathstats_[i].st; |
||||
|
|
||||
|
fmt::println(" {}: {}",i,path); |
||||
|
if(st.st_size < 0) |
||||
|
fmt::println(" - err: {}",strerror(-st.st_size)); |
||||
|
fmt::println(" -" |
||||
|
" uid: {};" |
||||
|
" gid: {};" |
||||
|
" mode: {:05o};" |
||||
|
" type: {};" |
||||
|
" size: {};" |
||||
|
" mtime: {:%Y-%m-%dT%H:%M:%S%z}", |
||||
|
st.st_uid, |
||||
|
st.st_gid, |
||||
|
st.st_mode, |
||||
|
::_file_type(st.st_mode), |
||||
|
st.st_size, |
||||
|
*std::localtime(&st.st_mtime)); |
||||
|
} |
||||
|
|
||||
|
if(!fix_func_) |
||||
|
return; |
||||
|
|
||||
|
if(::_files_same_type(pathstats_)) |
||||
|
fix_func_(pathstats_,copy_file_); |
||||
|
else |
||||
|
fmt::println(" X: WARNING - files are of different types." |
||||
|
" Requires manual intervention."); |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
int |
||||
|
_get_allpaths(const std::string &mergerfs_path_, |
||||
|
PathStatVec &pathstats_) |
||||
|
{ |
||||
|
int fd; |
||||
|
int rv; |
||||
|
IOCTL_BUF buf; |
||||
|
std::vector<std::string> allpaths; |
||||
|
|
||||
|
strcpy(buf,"allpaths"); |
||||
|
|
||||
|
fd = fs::open(mergerfs_path_,O_RDONLY|O_NOFOLLOW); |
||||
|
if(fd == -1) |
||||
|
return -errno; |
||||
|
|
||||
|
rv = fs::ioctl(fd,IOCTL_FILE_INFO,buf); |
||||
|
|
||||
|
fs::close(fd); |
||||
|
|
||||
|
str::split_on_null(buf,rv,&allpaths); |
||||
|
for(const auto &path : allpaths) |
||||
|
pathstats_.emplace_back(path); |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
u64 |
||||
|
_read_u64(const u64 min_, |
||||
|
const u64 max_) |
||||
|
{ |
||||
|
u64 rv; |
||||
|
while(true) |
||||
|
{ |
||||
|
fmt::print("Select correct file: "); |
||||
|
std::cin >> rv; |
||||
|
if((rv >= min_) && (rv <= max_)) |
||||
|
return rv; |
||||
|
std::cin.clear(); |
||||
|
std::cin.ignore(100, '\n'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
void |
||||
|
_setattr(const struct stat &st_, |
||||
|
const PathStatVec &pathstats_) |
||||
|
{ |
||||
|
int rv; |
||||
|
|
||||
|
for(const auto &path : pathstats_) |
||||
|
{ |
||||
|
rv = fs::lchmod(path.path,st_.st_mode); |
||||
|
rv |= fs::lchown(path.path,st_.st_uid,st_.st_gid); |
||||
|
if(rv) |
||||
|
fmt::println("ERROR: problem setting mode or owner - {}", |
||||
|
strerror(errno)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
void |
||||
|
_copy_files(const std::string &src_, |
||||
|
const PathStatVec &dsts_) |
||||
|
{ |
||||
|
for(const auto &dst : dsts_) |
||||
|
{ |
||||
|
int rv; |
||||
|
|
||||
|
// TODO: Additional protection to confirm not the literal same
|
||||
|
// file. fs::is_same_file(src,dst);
|
||||
|
if(src_ == dst.path) |
||||
|
continue; |
||||
|
|
||||
|
rv = fs::copyfile(src_,dst.path); |
||||
|
if(rv < 0) |
||||
|
fmt::print(stderr, |
||||
|
"ERROR: failed copying to {} - {}", |
||||
|
dst.path, |
||||
|
strerror(errno)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
bool |
||||
|
_mtime_cmp(const struct stat &a_, |
||||
|
const struct stat &b_) |
||||
|
{ |
||||
|
if(a_.st_mtim.tv_sec == b_.st_mtim.tv_sec) |
||||
|
return (a_.st_mtim.tv_nsec < b_.st_mtim.tv_nsec); |
||||
|
return (a_.st_mtim.tv_sec < b_.st_mtim.tv_sec); |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
bool |
||||
|
_pathstat_cmp(const PathStatVec::value_type &a_, |
||||
|
const PathStatVec::value_type &b_) |
||||
|
{ |
||||
|
return ::_mtime_cmp(a_.st,b_.st); |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
const |
||||
|
PathStatVec::value_type* |
||||
|
_find_latest_mtime(const PathStatVec &pathstats_) |
||||
|
{ |
||||
|
PathStatVec::const_iterator i; |
||||
|
|
||||
|
i = std::max_element(pathstats_.begin(), |
||||
|
pathstats_.end(), |
||||
|
::_pathstat_cmp); |
||||
|
|
||||
|
return &(*i); |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
const |
||||
|
PathStatVec::value_type* |
||||
|
_find_largest(const PathStatVec &pathstats_) |
||||
|
{ |
||||
|
PathStatVec::const_iterator i; |
||||
|
|
||||
|
i = std::max_element(pathstats_.begin(), |
||||
|
pathstats_.end(), |
||||
|
[](const PathStatVec::value_type &a_, |
||||
|
const PathStatVec::value_type &b_) |
||||
|
{ |
||||
|
return (a_.st.st_size < b_.st.st_size); |
||||
|
}); |
||||
|
|
||||
|
return &(*i); |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
void |
||||
|
_fix_func_manual(const PathStatVec &pathstats_, |
||||
|
const bool copy_file_) |
||||
|
{ |
||||
|
u64 idx; |
||||
|
|
||||
|
idx = ::_read_u64(0ULL,pathstats_.size()-1); |
||||
|
|
||||
|
if(copy_file_ && S_ISREG(pathstats_[idx].st.st_mode)) |
||||
|
::_copy_files(pathstats_[idx].path,pathstats_); |
||||
|
else |
||||
|
::_setattr(pathstats_[idx].st,pathstats_); |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
void |
||||
|
_fix_func_newest(const PathStatVec &pathstats_, |
||||
|
const bool copy_file_) |
||||
|
{ |
||||
|
const PathStatVec::value_type *pathst; |
||||
|
|
||||
|
pathst = _find_latest_mtime(pathstats_); |
||||
|
if(!pathst) |
||||
|
return; |
||||
|
|
||||
|
if(copy_file_ && S_ISREG(pathst->st.st_mode)) |
||||
|
::_copy_files(pathst->path,pathstats_); |
||||
|
else |
||||
|
::_setattr(pathst->st,pathstats_); |
||||
|
}; |
||||
|
|
||||
|
static |
||||
|
void |
||||
|
_fix_func_largest(const PathStatVec &pathstats_, |
||||
|
const bool copy_file_) |
||||
|
{ |
||||
|
const PathStatVec::value_type *pathst; |
||||
|
|
||||
|
pathst = ::_find_largest(pathstats_); |
||||
|
if(!pathst) |
||||
|
return; |
||||
|
|
||||
|
if(copy_file_ && S_ISREG(pathst->st.st_mode)) |
||||
|
::_copy_files(pathst->path,pathstats_); |
||||
|
else |
||||
|
::_setattr(pathst->st,pathstats_); |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
FixFunc |
||||
|
_select_fix_func(const std::string &fix_) |
||||
|
{ |
||||
|
if(fix_ == "none") |
||||
|
return {}; |
||||
|
else if(fix_ == "manual") |
||||
|
return ::_fix_func_manual; |
||||
|
else if(fix_ == "newest") |
||||
|
return ::_fix_func_newest; |
||||
|
else if(fix_ == "largest") |
||||
|
return ::_fix_func_largest; |
||||
|
else |
||||
|
abort(); |
||||
|
|
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
static |
||||
|
void |
||||
|
_fsck(const FS::path &path_, |
||||
|
const FixFunc fix_func_, |
||||
|
const bool check_size_, |
||||
|
const bool copy_file_) |
||||
|
{ |
||||
|
int rv; |
||||
|
PathStatVec paths; |
||||
|
|
||||
|
::_get_allpaths(path_,paths); |
||||
|
::_compare_files(path_,paths,fix_func_,check_size_,copy_file_); |
||||
|
|
||||
|
auto opts = FS::directory_options::skip_permission_denied; |
||||
|
auto rdi = FS::recursive_directory_iterator(path_,opts); |
||||
|
for(const auto &de : rdi) |
||||
|
{ |
||||
|
paths.clear(); |
||||
|
rv = ::_get_allpaths(de.path(),paths); |
||||
|
if(rv == -1) |
||||
|
continue; |
||||
|
|
||||
|
::_compare_files(de.path(), |
||||
|
paths, |
||||
|
fix_func_, |
||||
|
check_size_, |
||||
|
copy_file_); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int |
||||
|
fsck::main(int argc_, |
||||
|
char **argv_) |
||||
|
{ |
||||
|
CLI::App app; |
||||
|
FS::path path; |
||||
|
std::string fix; |
||||
|
bool check_size; |
||||
|
bool copy_file; |
||||
|
FixFunc fix_func; |
||||
|
|
||||
|
app.description("fsck.mergerfs:" |
||||
|
" A tool to help diagnose and solve mergerfs pool issues"); |
||||
|
app.name("USAGE: fsck.mergerfs"); |
||||
|
|
||||
|
app.add_option("path",path) |
||||
|
->description("mergerfs path") |
||||
|
->check(CLI::ExistingDirectory) |
||||
|
->required(); |
||||
|
app.add_option("--fix",fix) |
||||
|
->description("Will attempt to 'fix' the problem by chown+chmod or copying files based on a selected file.\n" |
||||
|
" * none: Do nothing. Just print details.\n" |
||||
|
" * manual: User selects source file.\n" |
||||
|
" * newest: Use file with most recent mtime.\n" |
||||
|
" * largest: Use file with largest size.") |
||||
|
->check(CLI::IsMember({"none","manual","newest","largest"})) |
||||
|
->default_val("none"); |
||||
|
app.add_option("--check-size",check_size) |
||||
|
->description("Considers file size in calculating differences") |
||||
|
->default_val(false) |
||||
|
->default_str("false"); |
||||
|
app.add_option("--copy-file",copy_file) |
||||
|
->description("Copy file rather than chown/chmod to fix") |
||||
|
->default_val(false) |
||||
|
->default_str("false"); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
app.parse(argc_,argv_); |
||||
|
} |
||||
|
catch(const CLI::ParseError &e) |
||||
|
{ |
||||
|
return app.exit(e); |
||||
|
} |
||||
|
|
||||
|
if((geteuid() != 0) && (fix != "none")) |
||||
|
fmt::println(stderr,"WARNING: fsck.mergerfs should be run as root to apply fixes"); |
||||
|
|
||||
|
fix_func = ::_select_fix_func(fix); |
||||
|
|
||||
|
::_fsck(path, |
||||
|
fix_func, |
||||
|
check_size, |
||||
|
copy_file); |
||||
|
|
||||
|
return 0; |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
#pragma once
|
||||
|
|
||||
|
namespace fsck |
||||
|
{ |
||||
|
int main(int argc, char **argv); |
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
#pragma once
|
||||
|
|
||||
|
#include <sys/ioctl.h>
|
||||
|
|
||||
|
#ifndef _IOC_TYPE
|
||||
|
#define _IOC_TYPE(X) (((X) >> 8) & 0xFF)
|
||||
|
#endif
|
||||
|
|
||||
|
typedef char IOCTL_BUF[4096]; |
||||
|
#define IOCTL_APP_TYPE 0xDF
|
||||
|
#define IOCTL_FILE_INFO _IOWR(IOCTL_APP_TYPE,0,IOCTL_BUF)
|
||||
|
#define IOCTL_GC _IO(IOCTL_APP_TYPE,1)
|
||||
|
#define IOCTL_GC1 _IO(IOCTL_APP_TYPE,2)
|
||||
|
#define IOCTL_INVALIDATE_ALL_NODES _IO(IOCTL_APP_TYPE,3)
|
||||
|
#define IOCTL_INVALIDATE_GID_CACHE _IO(IOCTL_APP_TYPE,4)
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue