From 93f7d7d927683d3fc4c2f0908991d72c41867320 Mon Sep 17 00:00:00 2001 From: Antonio SJ Musumeci Date: Tue, 2 Oct 2018 14:22:37 -0400 Subject: [PATCH] add link_cow feature When enabled if a regular file is opened which has a link count > 1 it will copy the file to a temporary file and rename over the original. Effectively breaking the link. This behavior is similar to cow-shell and other LD_PRELOAD based "CoW" solutions. --- README.md | 7 ++- man/mergerfs.1 | 19 ++++++- src/config.cpp | 1 + src/config.hpp | 13 ++--- src/fs_base_open.hpp | 37 +++++++++---- src/fs_base_rename.hpp | 15 ++++-- src/fs_base_unlink.hpp | 12 ++++- src/fs_clonefile.cpp | 115 +++++++++++++++------------------------ src/fs_clonefile.hpp | 9 ++-- src/fs_cow.cpp | 118 +++++++++++++++++++++++++++++++++++++++++ src/fs_cow.hpp | 33 ++++++++++++ src/fs_mktemp.cpp | 71 +++++++++++++++++++++++++ src/fs_mktemp.hpp | 28 ++++++++++ src/getxattr.cpp | 2 + src/listxattr.cpp | 3 +- src/open.cpp | 57 +++++++++++--------- src/option_parser.cpp | 5 ++ src/setxattr.cpp | 6 ++- 18 files changed, 424 insertions(+), 127 deletions(-) create mode 100644 src/fs_cow.cpp create mode 100644 src/fs_cow.hpp create mode 100644 src/fs_mktemp.cpp create mode 100644 src/fs_mktemp.hpp diff --git a/README.md b/README.md index 29f7d747..3cf1252a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ % mergerfs(1) mergerfs user manual % Antonio SJ Musumeci -% 2018-09-30 +% 2018-10-05 # NAME @@ -72,6 +72,7 @@ mergerfs does **not** support the copy-on-write (CoW) behavior found in **aufs** * **nullrw=true|false**: turns reads and writes into no-ops. The request will succeed but do nothing. Useful for benchmarking mergerfs. (default: false) * **ignorepponrename=true|false**: ignore path preserving on rename. Typically rename and link act differently depending on the policy of `create` (read below). Enabling this will cause rename and link to always use the non-path preserving behavior. This means files, when renamed or linked, will stay on the same drive. (default: false) * **security_capability=true|false**: If false return ENOATTR when xattr security.capability is queried. (default: true) +* **link_cow=true|false**: When enabled if a regular file is opened which has a link count > 1 it will copy the file to a temporary file and rename over the original. Breaking the link and providing a basic copy-on-write function similar to cow-shell. (default: false) * **threads=num**: number of threads to use in multithreaded mode. When set to zero (the default) it will attempt to discover and use the number of logical cores. If the lookup fails it will fall back to using 4. If the thread count is set negative it will look up the number of cores then divide by the absolute value. ie. threads=-2 on an 8 core machine will result in 8 / 2 = 4 threads. There will always be at least 1 thread. NOTE: higher number of threads increases parallelism but usually decreases throughput. (default: number of cores) *NOTE2:* the option is unavailable when built with system libfuse. * **fsname=name**: sets the name of the filesystem as seen in **mount**, **df**, etc. Defaults to a list of the source paths concatenated together with the longest common prefix removed. * **func.<func>=<policy>**: sets the specific FUSE function's policy. See below for the list of value types. Example: **func.getattr=newest** @@ -646,6 +647,10 @@ See the previous question's answer. Yes. You need to use `use_ino` to support proper reporting of inodes. Read the section "rename & link" for caveats. +#### Does mergerfs support CoW / copy-on-write? + +Not in the sense of a filesystem like BTRFS or ZFS nor in the overlayfs or aufs sense. It does offer a [cow-shell](http://manpages.ubuntu.com/manpages/bionic/man1/cow-shell.1.html) like hardlink breaking (copy to temp file then rename over original) which can be useful when wanting to save space by hardlinking duplicate files but wish to treat each name as if it were a unique and separate file. + #### Why can't I see my files / directories? It's almost always a permissions issue. Unlike mhddfs, which runs as root and attempts to access content as such, mergerfs always changes it's credentials to that of the caller. This means that if the user does not have access to a file or directory than neither will mergerfs. However, because mergerfs is creating a union of paths it may be able to read some files and directories on one drive but not another resulting in an incomplete set. diff --git a/man/mergerfs.1 b/man/mergerfs.1 index 3f6bb3ca..1c329039 100644 --- a/man/mergerfs.1 +++ b/man/mergerfs.1 @@ -1,7 +1,7 @@ .\"t .\" Automatically generated by Pandoc 1.19.2.4 .\" -.TH "mergerfs" "1" "2018\-09\-30" "mergerfs user manual" "" +.TH "mergerfs" "1" "2018\-10\-05" "mergerfs user manual" "" .hy .SH NAME .PP @@ -156,6 +156,13 @@ This means files, when renamed or linked, will stay on the same drive. xattr security.capability is queried. (default: true) .IP \[bu] 2 +\f[B]link_cow=true|false\f[]: When enabled if a regular file is opened +which has a link count > 1 it will copy the file to a temporary file and +rename over the original. +Breaking the link and providing a basic copy\-on\-write function similar +to cow\-shell. +(default: false) +.IP \[bu] 2 \f[B]threads=num\f[]: number of threads to use in multithreaded mode. When set to zero (the default) it will attempt to discover and use the number of logical cores. @@ -1361,6 +1368,16 @@ See the previous question\[aq]s answer. Yes. You need to use \f[C]use_ino\f[] to support proper reporting of inodes. Read the section "rename & link" for caveats. +.SS Does mergerfs support CoW / copy\-on\-write? +.PP +Not in the sense of a filesystem like BTRFS or ZFS nor in the overlayfs +or aufs sense. +It does offer a +cow\-shell (http://manpages.ubuntu.com/manpages/bionic/man1/cow-shell.1.html) +like hardlink breaking (copy to temp file then rename over original) +which can be useful when wanting to save space by hardlinking duplicate +files but wish to treat each name as if it were a unique and separate +file. .SS Why can\[aq]t I see my files / directories? .PP It\[aq]s almost always a permissions issue. diff --git a/src/config.cpp b/src/config.cpp index a6c3e069..e0c8f5cd 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -46,6 +46,7 @@ namespace mergerfs nullrw(false), ignorepponrename(false), security_capability(true), + link_cow(false), POLICYINIT(access), POLICYINIT(chmod), POLICYINIT(chown), diff --git a/src/config.hpp b/src/config.hpp index 40c6383b..d9284067 100644 --- a/src/config.hpp +++ b/src/config.hpp @@ -16,17 +16,17 @@ #pragma once +#include "fusefunc.hpp" +#include "policy.hpp" + #include -#include #include +#include #include #include -#include "policy.hpp" -#include "fusefunc.hpp" - namespace mergerfs { class Config @@ -37,8 +37,8 @@ namespace mergerfs public: int set_func_policy(const std::string &fusefunc_, const std::string &policy_); - int set_category_policy(const std::string &category, - const std::string &policy); + int set_category_policy(const std::string &category_, + const std::string &policy_); public: std::string destmount; @@ -53,6 +53,7 @@ namespace mergerfs bool nullrw; bool ignorepponrename; bool security_capability; + bool link_cow; public: const Policy *policies[FuseFunc::Enum::END]; diff --git a/src/fs_base_open.hpp b/src/fs_base_open.hpp index 8a0a1de3..364ad003 100644 --- a/src/fs_base_open.hpp +++ b/src/fs_base_open.hpp @@ -18,30 +18,49 @@ #pragma once -#include - #include #include #include +#include + namespace fs { static inline int - open(const std::string &path, - const int flags) + open(const char *path_, + const int flags_) + { + return ::open(path_,flags_); + } + + static + inline + int + open(const char *path_, + const int flags_, + const mode_t mode_) + { + return ::open(path_,flags_,mode_); + } + + static + inline + int + open(const std::string &path_, + const int flags_) { - return ::open(path.c_str(),flags); + return fs::open(path_.c_str(),flags_); } static inline int - open(const std::string &path, - const int flags, - const mode_t mode) + open(const std::string &path_, + const int flags_, + const mode_t mode_) { - return ::open(path.c_str(),flags,mode); + return fs::open(path_.c_str(),flags_,mode_); } } diff --git a/src/fs_base_rename.hpp b/src/fs_base_rename.hpp index b218efea..7bfdba7e 100644 --- a/src/fs_base_rename.hpp +++ b/src/fs_base_rename.hpp @@ -25,9 +25,18 @@ namespace fs static inline int - rename(const std::string &oldpath, - const std::string &newpath) + rename(const char *oldpath_, + const char *newpath_) { - return ::rename(oldpath.c_str(),newpath.c_str()); + return ::rename(oldpath_,newpath_); + } + + static + inline + int + rename(const std::string &oldpath_, + const std::string &newpath_) + { + return fs::rename(oldpath_.c_str(),newpath_.c_str()); } } diff --git a/src/fs_base_unlink.hpp b/src/fs_base_unlink.hpp index a3b2d72a..08828595 100644 --- a/src/fs_base_unlink.hpp +++ b/src/fs_base_unlink.hpp @@ -27,8 +27,16 @@ namespace fs static inline int - unlink(const std::string &path) + unlink(const char *path_) { - return ::unlink(path.c_str()); + return ::unlink(path_); + } + + static + inline + int + unlink(const std::string &path_) + { + return fs::unlink(path_.c_str()); } } diff --git a/src/fs_clonefile.cpp b/src/fs_clonefile.cpp index 0c3ceb7c..5e034c9a 100644 --- a/src/fs_clonefile.cpp +++ b/src/fs_clonefile.cpp @@ -14,12 +14,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include -#include - -#include -#include - #include "errno.hpp" #include "fs_attr.hpp" #include "fs_base_chmod.hpp" @@ -37,6 +31,12 @@ #include "fs_sendfile.hpp" #include "fs_xattr.hpp" +#include +#include + +#include +#include + #ifndef O_LARGEFILE # define O_LARGEFILE 0 #endif @@ -49,36 +49,36 @@ using std::string; using std::vector; int -writen(const int fd, - const char *buf, - const size_t count) +writen(const int fd_, + const char *buf_, + const size_t count_) { size_t nleft; ssize_t nwritten; - nleft = count; + nleft = count_; do { - nwritten = fs::write(fd,buf,nleft); + nwritten = fs::write(fd_,buf_,nleft); if((nwritten == -1) && (errno == EINTR)) continue; if(nwritten == -1) return -1; nleft -= nwritten; - buf += nwritten; + buf_ += nwritten; } while(nleft > 0); - return count; + return count_; } static int -copyfile_rw(const int fdin, - const int fdout, - const size_t count, - const size_t blocksize) +copyfile_rw(const int src_fd_, + const int dst_fd_, + const size_t count_, + const size_t blocksize_) { ssize_t nr; ssize_t nw; @@ -86,15 +86,15 @@ copyfile_rw(const int fdin, size_t totalwritten; vector buf; - bufsize = (blocksize * 16); + bufsize = (blocksize_ * 16); buf.resize(bufsize); - fs::lseek(fdin,0,SEEK_SET); + fs::lseek(src_fd_,0,SEEK_SET); totalwritten = 0; - while(totalwritten < count) + while(totalwritten < count_) { - nr = fs::read(fdin,&buf[0],bufsize); + nr = fs::read(src_fd_,&buf[0],bufsize); if(nr == 0) return totalwritten; if((nr == -1) && (errno == EINTR)) @@ -102,7 +102,7 @@ copyfile_rw(const int fdin, if(nr == -1) return -1; - nw = writen(fdout,&buf[0],nr); + nw = writen(dst_fd_,&buf[0],nr); if(nw == -1) return -1; @@ -114,30 +114,30 @@ copyfile_rw(const int fdin, static int -copydata(const int fdin, - const int fdout, - const size_t count, - const size_t blocksize) +copydata(const int src_fd_, + const int dst_fd_, + const size_t count_, + const size_t blocksize_) { int rv; - fs::fadvise_willneed(fdin,0,count); - fs::fadvise_sequential(fdin,0,count); + fs::fadvise_willneed(src_fd_,0,count_); + fs::fadvise_sequential(src_fd_,0,count_); - fs::fallocate(fdout,0,0,count); + fs::fallocate(dst_fd_,0,0,count_); - rv = fs::sendfile(fdin,fdout,count); + rv = fs::sendfile(src_fd_,dst_fd_,count_); if((rv == -1) && ((errno == EINVAL) || (errno == ENOSYS))) - return ::copyfile_rw(fdin,fdout,count,blocksize); + return ::copyfile_rw(src_fd_,dst_fd_,count_,blocksize_); return rv; } static bool -ignorable_error(const int err) +ignorable_error(const int err_) { - switch(err) + switch(err_) { case ENOTTY: case ENOTSUP: @@ -153,69 +153,40 @@ ignorable_error(const int err) namespace fs { int - clonefile(const int fdin, - const int fdout) + clonefile(const int src_fd_, + const int dst_fd_) { int rv; - struct stat stin; + struct stat src_st; - rv = fs::fstat(fdin,stin); + rv = fs::fstat(src_fd_,src_st); if(rv == -1) return -1; - rv = ::copydata(fdin,fdout,stin.st_size,stin.st_blksize); + rv = ::copydata(src_fd_,dst_fd_,src_st.st_size,src_st.st_blksize); if(rv == -1) return -1; - rv = fs::attr::copy(fdin,fdout); + rv = fs::attr::copy(src_fd_,dst_fd_); if((rv == -1) && !ignorable_error(errno)) return -1; - rv = fs::xattr::copy(fdin,fdout); + rv = fs::xattr::copy(src_fd_,dst_fd_); if((rv == -1) && !ignorable_error(errno)) return -1; - rv = fs::fchown_check_on_error(fdout,stin); + rv = fs::fchown_check_on_error(dst_fd_,src_st); if(rv == -1) return -1; - rv = fs::fchmod_check_on_error(fdout,stin); + rv = fs::fchmod_check_on_error(dst_fd_,src_st); if(rv == -1) return -1; - rv = fs::utime(fdout,stin); + rv = fs::utime(dst_fd_,src_st); if(rv == -1) return -1; return 0; } - - int - clonefile(const string &in, - const string &out) - { - int rv; - int fdin; - int fdout; - int error; - - fdin = fs::open(in,O_RDONLY|O_NOFOLLOW); - if(fdin == -1) - return -1; - - const int flags = O_CREAT|O_LARGEFILE|O_NOATIME|O_NOFOLLOW|O_TRUNC|O_WRONLY; - const mode_t mode = S_IWUSR; - fdout = fs::open(out,flags,mode); - if(fdout == -1) - return -1; - - rv = fs::clonefile(fdin,fdout); - error = errno; - - fs::close(fdin); - fs::close(fdout); - - errno = error; - return rv; - } } diff --git a/src/fs_clonefile.hpp b/src/fs_clonefile.hpp index 101514db..42ff0278 100644 --- a/src/fs_clonefile.hpp +++ b/src/fs_clonefile.hpp @@ -16,12 +16,9 @@ #pragma once -#include - namespace fs { - int clonefile(const int fdin, - const int fdout); - int clonefile(const std::string &from, - const std::string &to); + int + clonefile(const int src_fd_, + const int dst_fd_); } diff --git a/src/fs_cow.cpp b/src/fs_cow.cpp new file mode 100644 index 00000000..83bc9396 --- /dev/null +++ b/src/fs_cow.cpp @@ -0,0 +1,118 @@ +/* + ISC License + + Copyright (c) 2018, 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. +*/ + +#include "fs_clonefile.hpp" +#include "fs_mktemp.hpp" + +#include "fs_base_close.hpp" +#include "fs_base_open.hpp" +#include "fs_base_rename.hpp" +#include "fs_base_stat.hpp" +#include "fs_base_unlink.hpp" + +#include +#include +#include +#include +#include + +#include + +using std::string; + +static +int +cleanup_on_error(const int src_fd_, + const int dst_fd_ = -1, + const string &dst_fullpath_ = string()) +{ + int error = errno; + + if(src_fd_ >= 0) + fs::close(src_fd_); + if(dst_fd_ >= 0) + fs::close(dst_fd_); + if(!dst_fullpath_.empty()) + fs::unlink(dst_fullpath_); + + errno = error; + + return -1; +} + + +namespace fs +{ + namespace cow + { + bool + is_eligible(const int flags_, + const struct stat &st_) + { + return (((flags_ & O_RDWR) || (flags_ & O_WRONLY)) && + (S_ISREG(st_.st_mode)) && + (st_.st_nlink > 1)); + } + + bool + is_eligible(const char *fullpath_, + const int flags_) + { + int rv; + struct stat st; + + rv = fs::stat(fullpath_,st); + if(rv == -1) + return false; + + return fs::cow::is_eligible(flags_,st); + } + + int + break_link(const char *src_fullpath_) + { + int rv; + int src_fd; + int dst_fd; + string dst_fullpath; + + src_fd = fs::open(src_fullpath_,O_RDONLY|O_NOFOLLOW|O_LARGEFILE); + if(src_fd == -1) + return -1; + + dst_fullpath = src_fullpath_; + + dst_fd = fs::mktemp(dst_fullpath,O_WRONLY|O_LARGEFILE); + if(dst_fd == -1) + return cleanup_on_error(src_fd); + + rv = fs::clonefile(src_fd,dst_fd); + if(rv == -1) + return cleanup_on_error(src_fd,dst_fd,dst_fullpath); + + rv = fs::rename(dst_fullpath,src_fullpath_); + if(rv == -1) + return cleanup_on_error(src_fd,dst_fd,dst_fullpath); + + fs::close(src_fd); + fs::close(dst_fd); + + return 0; + } + } +} diff --git a/src/fs_cow.hpp b/src/fs_cow.hpp new file mode 100644 index 00000000..8498501d --- /dev/null +++ b/src/fs_cow.hpp @@ -0,0 +1,33 @@ +/* + ISC License + + Copyright (c) 2018, 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 +#include + +namespace fs +{ + namespace cow + { + bool is_eligible(const int flags_, const struct stat &st_); + bool is_eligible(const char *fullpath_, const int flags_); + + int break_link(const char *fullpath_); + } +} diff --git a/src/fs_mktemp.cpp b/src/fs_mktemp.cpp new file mode 100644 index 00000000..ee9fef26 --- /dev/null +++ b/src/fs_mktemp.cpp @@ -0,0 +1,71 @@ +/* + ISC License + + Copyright (c) 2018, 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. +*/ + +#include "fs_base_open.hpp" + +#include +#include + +#include + +using std::string; + +static +string +generate_tmp_path(const string &base_) +{ + string tmp; + + tmp = base_; + tmp += '_'; + for(int i = 0; i < 6; i++) + tmp += ('A' + (std::rand() % 26)); + + return tmp; +} + +namespace fs +{ + int + mktemp(string &base_, + const int flags_) + { + int fd; + int count; + int flags; + string tmppath; + + fd = -1; + count = 10; + flags = (flags_ | O_EXCL | O_CREAT); + while(count-- > 0) + { + tmppath = generate_tmp_path(base_); + + fd = fs::open(tmppath,flags,S_IWUSR); + if((fd == -1) && (errno == EEXIST)) + continue; + else if(fd != -1) + base_ = tmppath; + + return fd; + } + + return (errno=EEXIST,-1); + } +} diff --git a/src/fs_mktemp.hpp b/src/fs_mktemp.hpp new file mode 100644 index 00000000..64aaa5f8 --- /dev/null +++ b/src/fs_mktemp.hpp @@ -0,0 +1,28 @@ +/* + ISC License + + Copyright (c) 2018, 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 + +namespace fs +{ + int + mktemp(std::string &base_, + const int flags_); +} diff --git a/src/getxattr.cpp b/src/getxattr.cpp index 72760e70..2dd6456c 100644 --- a/src/getxattr.cpp +++ b/src/getxattr.cpp @@ -210,6 +210,8 @@ _getxattr_controlfile(const Config &config, _getxattr_controlfile_bool(config.ignorepponrename,attrvalue); else if(attr[2] == "security_capability") _getxattr_controlfile_bool(config.security_capability,attrvalue); + else if(attr[2] == "link_cow") + _getxattr_controlfile_bool(config.link_cow,attrvalue); else if(attr[2] == "policies") _getxattr_controlfile_policies(config,attrvalue); else if(attr[2] == "version") diff --git a/src/listxattr.cpp b/src/listxattr.cpp index 357d46c8..63ab5392 100644 --- a/src/listxattr.cpp +++ b/src/listxattr.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2016, Antonio SJ Musumeci + Copyright (c) 2018, 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 @@ -52,6 +52,7 @@ _listxattr_controlfile(char *list, ("user.mergerfs.nullrw") ("user.mergerfs.ignorepponrename") ("user.mergerfs.security_capability") + ("user.mergerfs.link_cow") ("user.mergerfs.policies") ("user.mergerfs.version") ("user.mergerfs.pid"); diff --git a/src/open.cpp b/src/open.cpp index 78218ba2..43dbadd5 100644 --- a/src/open.cpp +++ b/src/open.cpp @@ -16,61 +16,67 @@ #include -#include - -#include -#include - #include "config.hpp" #include "errno.hpp" #include "fileinfo.hpp" #include "fs_base_open.hpp" +#include "fs_cow.hpp" #include "fs_path.hpp" #include "rwlock.hpp" #include "ugid.hpp" +#include + +#include +#include + using std::string; using std::vector; using mergerfs::Policy; static int -_open_core(const string *basepath, - const char *fusepath, - const int flags, - uint64_t &fh) +_open_core(const string *basepath_, + const char *fusepath_, + const int flags_, + const bool link_cow_, + uint64_t &fh_) { int fd; string fullpath; - fs::path::make(basepath,fusepath,fullpath); + fs::path::make(basepath_,fusepath_,fullpath); + + if(link_cow_ && fs::cow::is_eligible(fullpath.c_str(),flags_)) + fs::cow::break_link(fullpath.c_str()); - fd = fs::open(fullpath,flags); + fd = fs::open(fullpath,flags_); if(fd == -1) return -errno; - fh = reinterpret_cast(new FileInfo(fd,fusepath)); + fh_ = reinterpret_cast(new FileInfo(fd,fusepath_)); return 0; } static int -_open(Policy::Func::Search searchFunc, - const vector &srcmounts, - const uint64_t minfreespace, - const char *fusepath, - const int flags, - uint64_t &fh) +_open(Policy::Func::Search searchFunc_, + const vector &srcmounts_, + const uint64_t minfreespace_, + const char *fusepath_, + const int flags_, + const bool link_cow_, + uint64_t &fh_) { int rv; vector basepaths; - rv = searchFunc(srcmounts,fusepath,minfreespace,basepaths); + rv = searchFunc_(srcmounts_,fusepath_,minfreespace_,basepaths); if(rv == -1) return -errno; - return _open_core(basepaths[0],fusepath,flags,fh); + return _open_core(basepaths[0],fusepath_,flags_,link_cow_,fh_); } namespace mergerfs @@ -78,8 +84,8 @@ namespace mergerfs namespace fuse { int - open(const char *fusepath, - fuse_file_info *ffi) + open(const char *fusepath_, + fuse_file_info *ffi_) { const fuse_context *fc = fuse_get_context(); const Config &config = Config::get(fc); @@ -89,9 +95,10 @@ namespace mergerfs return _open(config.open, config.srcmounts, config.minfreespace, - fusepath, - ffi->flags, - ffi->fh); + fusepath_, + ffi_->flags, + config.link_cow, + ffi_->fh); } } } diff --git a/src/option_parser.cpp b/src/option_parser.cpp index 93cac5ce..7cf093c0 100644 --- a/src/option_parser.cpp +++ b/src/option_parser.cpp @@ -197,6 +197,8 @@ parse_and_process_kv_arg(Config &config, rv = parse_and_process(value,config.ignorepponrename); else if(key == "security_capability") rv = parse_and_process(value,config.security_capability); + else if(key == "link_cow") + rv = parse_and_process(value,config.link_cow); } if(rv == -1) @@ -299,12 +301,15 @@ usage(void) " timeout in seconds before will turn to symlinks.\n" " default = 3600\n" " -o nullrw= Disables reads and writes. For benchmarking.\n" + " default = false\n" " -o ignorepponrename=\n" " Ignore path preserving when performing renames\n" " and links. default = false\n" " -o security_capability=\n" " When disabled return ENOATTR when the xattr\n" " security.capability is queried. default = true\n" + " -o link_cow= delink/clone file on open to simulate CoW.\n" + " default = false\n" << std::endl; } diff --git a/src/setxattr.cpp b/src/setxattr.cpp index e01fdffd..7d24d088 100644 --- a/src/setxattr.cpp +++ b/src/setxattr.cpp @@ -1,5 +1,5 @@ /* - Copyright (c) 2016, Antonio SJ Musumeci + Copyright (c) 2018, 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 @@ -302,6 +302,10 @@ _setxattr_controlfile(Config &config, return _setxattr_bool(attrval, flags, config.security_capability); + else if(attr[2] == "link_cow") + return _setxattr_bool(attrval, + flags, + config.link_cow); break; case 4: