Browse Source

new features: follow-symlinks, rename-exdev, link-exdev

* follow-symlinks: allows mergerfs to transparently follow symlinks
* link-exdev: in the event a link returns EXDEV create a symlink instead
* rename-exdev: in the event a rename returns EXDEV move the oldpath and
  create a symlink for the newpath
pull/883/head
Antonio SJ Musumeci 4 years ago
parent
commit
8adebc9489
  1. 2
      Makefile
  2. 51
      README.md
  3. 2
      libfuse/Makefile
  4. 17
      libfuse/include/fuse.h
  5. 158
      libfuse/lib/fuse.c
  6. 81
      man/mergerfs.1
  7. 8
      src/config.cpp
  8. 9
      src/config.hpp
  9. 58
      src/config_follow_symlinks.cpp
  10. 30
      src/config_follow_symlinks.hpp
  11. 58
      src/config_link_exdev.cpp
  12. 30
      src/config_link_exdev.hpp
  13. 54
      src/config_rename_exdev.cpp
  14. 29
      src/config_rename_exdev.hpp
  15. 4
      src/enum.hpp
  16. 22
      src/fs_mkdir.hpp
  17. 35
      src/fs_mkdir_as_root.hpp
  18. 21
      src/fs_symlink.hpp
  19. 66
      src/fuse_getattr.cpp
  20. 324
      src/fuse_link.cpp
  21. 8
      src/fuse_link.hpp
  22. 544
      src/fuse_rename.cpp
  23. 34
      src/fuse_rmdir.cpp
  24. 50
      src/fuse_symlink.cpp
  25. 15
      src/fuse_symlink.hpp
  26. 5742
      src/ghc/filesystem.hpp

2
Makefile

@ -40,7 +40,7 @@ USE_XATTR = 1
UGID_USE_RWLOCK = 0
ifeq ($(DEBUG),1)
OPT_FLAGS := -O0 -g
OPT_FLAGS := -O0 -g -fsanitize=undefined
else
OPT_FLAGS := -O2
endif

51
README.md

@ -113,6 +113,9 @@ These options are the same regardless you use them with the `mergerfs` commandli
* **statfs=base|full**: Controls how statfs works. 'base' means it will always use all branches in statfs calculations. 'full' is in effect path preserving and only includes drives where the path exists. (default: base)
* **statfs_ignore=none|ro|nc**: 'ro' will cause statfs calculations to ignore available space for branches mounted or tagged as 'read-only' or 'no create'. 'nc' will ignore available space for branches tagged as 'no create'. (default: none)
* **nfsopenhack=off|git|all**: A workaround for exporting mergerfs over NFS where there are issues with creating files for write while setting the mode to read-only. (default: off)
* **follow-symlinks=never|directory|regular|all**: Turns symlinks into what they point to. (default: never)
* **link-exdev=passthrough|rel-symlink|abs-base-symlink|abs-pool-symlink**: When a link fails with EXDEV optionally create a symlink to the file instead.
* **rename-exdev=passthrough|rel-symlink|abs-symlink**: When a rename fails with EXDEV optionally move the file to a special directory and symlink to it.
* **posix_acl=BOOL**: Enable POSIX ACL support (if supported by kernel and underlying filesystem). (default: false)
* **async_read=BOOL**: Perform reads asynchronously. If disabled or unavailable the kernel will ensure there is at most one pending read request per file handle and will attempt to order requests by offset. (default: true)
* **fuse_msg_size=UINT**: Set the max number of pages per FUSE message. Only available on Linux >= 4.20 and ignored otherwise. (min: 1; max: 256; default: 256)
@ -240,6 +243,54 @@ In Linux 4.20 a new feature was added allowing the negotiation of the max messag
Since there should be no downsides to increasing `fuse_msg_size` / `max_pages`, outside a minor bump in RAM usage due to larger message buffers, mergerfs defaults the value to 256. On kernels before 4.20 the value has no effect. The reason the value is configurable is to enable experimentation and benchmarking. See the BENCHMARKING section for examples.
### follow-symlinks
This feature, when enabled, will cause symlinks to be interpreted by mergerfs as their target (depending on the mode).
When there is a getattr/stat request for a file mergerfs will check if the file is a symlink and depending on the `follow-symlinks` setting will replace the information about the symlink with that of that which it points to.
When unlink'ing or rmdir'ing the followed symlink it will remove the symlink itself and not that which it points to.
* never: Behave as normal. Symlinks are treated as such.
* directory: Resolve symlinks only which point to directories.
* regular: Resolve symlinks only which point to regular files.
* all: Resolve all symlinks to that which they point to.
Symlinks which do not point to anything are left as is.
WARNING: This feature works but there might be edge cases yet found. If you find any odd behaviors please file a ticket on [github](https://github.com/trapexit/mergerfs/issues).
### link-exdev
If using path preservation and a `link` fails with EXDEV make a call to `symlink` where the `target` is the `oldlink` and the `linkpath` is the `newpath`. The `target` value is determined by the value of `link-exdev`.
* passthrough: Return EXDEV as normal.
* rel-symlink: A relative path from the `newpath`.
* abs-base-symlink: A absolute value using the underlying branch.
* abs-pool-symlink: A absolute value using the mergerfs mount point.
NOTE: It is possible that some applications check the file they link. In those cases it is possible it will error or complain.
### rename-exdev
If using path preservation and a `rename` fails with EXDEV:
1. Move file from **/branch/a/b/c** to **/branch/.mergerfs_rename_exdev/a/b/c**.
2. symlink the rename's `newpath` to the moved file.
The `target` value is determined by the value of `rename-exdev`.
* passthrough: Return EXDEV as normal.
* rel-symlink: A relative path from the `newpath`.
* abs-symlink: A absolute value using the mergerfs mount point.
NOTE: It is possible that some applications check the file they rename. In those cases it is possible it will error or complain.
NOTE: The reason `abs-symlink` is not split into two like `link-exdev` is due to the complexities in managing absolute base symlinks when multiple `oldpaths` exist.
### symlinkify
Due to the levels of indirection introduced by mergerfs and the underlying technology FUSE there can be varying levels of performance degradation. This feature will turn non-directories which are not writable into symlinks to the original file found by the `readlink` policy after the mtime and ctime are older than the timeout.

2
libfuse/Makefile

@ -10,7 +10,7 @@ INSTALLUTILS :=
endif
ifeq ($(DEBUG),1)
OPT_FLAGS := -O0 -g
OPT_FLAGS := -O0 -g -fsanitize=undefined
else
OPT_FLAGS := -O2
endif

17
libfuse/include/fuse.h

@ -121,13 +121,13 @@ struct fuse_operations
int (*rmdir) (const char *);
/** Create a symbolic link */
int (*symlink) (const char *, const char *);
int (*symlink) (const char *, const char *, struct stat *, fuse_timeouts_t *);
/** Rename a file */
int (*rename) (const char *, const char *);
/** Create a hard link to a file */
int (*link) (const char *, const char *);
int (*link) (const char *, const char *, struct stat *, fuse_timeouts_t *);
/** Change the permission bits of a file */
int (*chmod) (const char *, mode_t);
@ -794,9 +794,16 @@ int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
const char *newpath);
int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
const char *path);
int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
int fuse_fs_symlink(struct fuse_fs *fs,
const char *linkname,
const char *path,
struct stat *st,
fuse_timeouts_t *timeouts);
int fuse_fs_link(struct fuse_fs *fs,
const char *oldpath,
const char *newpath,
struct stat *st,
fuse_timeouts_t *timeouts);
int fuse_fs_release(struct fuse_fs *fs,
fuse_file_info_t *fi);
int fuse_fs_open(struct fuse_fs *fs, const char *path,

158
libfuse/lib/fuse.c

@ -1650,25 +1650,30 @@ fuse_fs_rmdir(struct fuse_fs *fs,
}
int
fuse_fs_symlink(struct fuse_fs *fs,
const char *linkname,
const char *path)
fuse_fs_symlink(struct fuse_fs *fs_,
const char *linkname_,
const char *path_,
struct stat *st_,
fuse_timeouts_t *timeouts_)
{
if(fs->op.symlink == NULL)
if(fs_->op.symlink == NULL)
return -ENOSYS;
fuse_get_context()->private_data = fs->user_data;
if(fs_->debug)
fprintf(stderr,"symlink %s %s\n",linkname_,path_);
if(fs->debug)
fprintf(stderr,"symlink %s %s\n",linkname,path);
fuse_get_context()->private_data = fs_->user_data;
return fs->op.symlink(linkname,path);
return fs_->op.symlink(linkname_,path_,st_,timeouts_);
}
int
fuse_fs_link(struct fuse_fs *fs,
const char *oldpath,
const char *newpath)
fuse_fs_link(struct fuse_fs *fs,
const char *oldpath,
const char *newpath,
struct stat *st_,
fuse_timeouts_t *timeouts_)
{
if(fs->op.link == NULL)
return -ENOSYS;
@ -1678,7 +1683,7 @@ fuse_fs_link(struct fuse_fs *fs,
if(fs->debug)
fprintf(stderr,"link %s %s\n",oldpath,newpath);
return fs->op.link(oldpath,newpath);
return fs->op.link(oldpath,newpath,st_,timeouts_);
}
int
@ -2541,6 +2546,37 @@ update_stat(struct node *node_,
node_->mtim = stnew_->st_mtim;
}
static
int
set_path_info(struct fuse *f,
fuse_ino_t nodeid,
const char *name,
struct fuse_entry_param *e)
{
struct node *node;
node = find_node(f,nodeid,name);
if(node == NULL)
return -ENOMEM;
e->ino = node->nodeid;
e->generation = node->generation;
pthread_mutex_lock(&f->lock);
update_stat(node,&e->attr);
pthread_mutex_unlock(&f->lock);
set_stat(f,e->ino,&e->attr);
if(f->conf.debug)
fprintf(stderr,
" NODEID: %llu\n"
" GEN: %llu\n",
(unsigned long long)e->ino,
(unsigned long long)e->generation);
return 0;
}
static
int
lookup_path(struct fuse *f,
@ -2550,43 +2586,18 @@ lookup_path(struct fuse *f,
struct fuse_entry_param *e,
fuse_file_info_t *fi)
{
int res;
int rv;
memset(e,0,sizeof(struct fuse_entry_param));
res = ((fi == NULL) ?
fuse_fs_getattr(f->fs,path,&e->attr,&e->timeout) :
fuse_fs_fgetattr(f->fs,&e->attr,fi,&e->timeout));
if(res == 0)
{
struct node *node;
node = find_node(f,nodeid,name);
if(node == NULL)
{
res = -ENOMEM;
}
else
{
e->ino = node->nodeid;
e->generation = node->generation;
rv = ((fi == NULL) ?
fuse_fs_getattr(f->fs,path,&e->attr,&e->timeout) :
fuse_fs_fgetattr(f->fs,&e->attr,fi,&e->timeout));
pthread_mutex_lock(&f->lock);
update_stat(node,&e->attr);
pthread_mutex_unlock(&f->lock);
set_stat(f,e->ino,&e->attr);
if(f->conf.debug)
fprintf(stderr,
" NODEID: %llu\n"
" GEN: %llu\n",
(unsigned long long)e->ino,
(unsigned long long)e->generation);
}
}
if(rv)
return rv;
return res;
return set_path_info(f,nodeid,name,e);
}
static
@ -3236,26 +3247,28 @@ fuse_lib_rmdir(fuse_req_t req,
static
void
fuse_lib_symlink(fuse_req_t req,
const char *linkname,
fuse_ino_t parent,
const char *name)
fuse_lib_symlink(fuse_req_t req_,
const char *linkname_,
fuse_ino_t parent_,
const char *name_)
{
struct fuse *f = req_fuse_prepare(req);
struct fuse_entry_param e;
int rv;
char *path;
int err;
struct fuse *f;
struct fuse_entry_param e = {0};
err = get_path_name(f,parent,name,&path);
if(!err)
f = req_fuse_prepare(req_);
rv = get_path_name(f,parent_,name_,&path);
if(rv == 0)
{
err = fuse_fs_symlink(f->fs,linkname,path);
if(!err)
err = lookup_path(f,parent,name,path,&e,NULL);
free_path(f,parent,path);
rv = fuse_fs_symlink(f->fs,linkname_,path,&e.attr,&e.timeout);
if(rv == 0)
rv = set_path_info(f,parent_,name_,&e);
free_path(f,parent_,path);
}
reply_entry(req,&e,err);
reply_entry(req_,&e,rv);
}
static
@ -3298,27 +3311,32 @@ fuse_lib_rename(fuse_req_t req,
reply_err(req,err);
}
static void fuse_lib_link(fuse_req_t req,fuse_ino_t ino,fuse_ino_t newparent,
const char *newname)
static
void
fuse_lib_link(fuse_req_t req,
fuse_ino_t ino,
fuse_ino_t newparent,
const char *newname)
{
struct fuse *f = req_fuse_prepare(req);
struct fuse_entry_param e;
int rv;
char *oldpath;
char *newpath;
int err;
struct fuse *f;
struct fuse_entry_param e = {0};
f = req_fuse_prepare(req);
err = get_path2(f,ino,NULL,newparent,newname,
rv = get_path2(f,ino,NULL,newparent,newname,
&oldpath,&newpath,NULL,NULL);
if(!err)
if(!rv)
{
err = fuse_fs_link(f->fs,oldpath,newpath);
if(!err)
err = lookup_path(f,newparent,newname,newpath,
&e,NULL);
rv = fuse_fs_link(f->fs,oldpath,newpath,&e.attr,&e.timeout);
if(rv == 0)
rv = set_path_info(f,newparent,newname,&e);
free_path2(f,ino,newparent,NULL,NULL,oldpath,newpath);
}
reply_entry(req,&e,err);
reply_entry(req,&e,rv);
}
static

81
man/mergerfs.1

@ -223,6 +223,18 @@ over NFS where there are issues with creating files for write while
setting the mode to read\-only.
(default: off)
.IP \[bu] 2
\f[B]follow\-symlinks=never|directory|regular|all\f[]: Turns symlinks
into what they point to.
(default: never)
.IP \[bu] 2
\f[B]link\-exdev=passthrough|rel\-symlink|abs\-base\-symlink|abs\-pool\-symlink\f[]:
When a link fails with EXDEV optionally create a symlink to the file
instead.
.IP \[bu] 2
\f[B]rename\-exdev=passthrough|rel\-symlink|abs\-symlink\f[]: When a
rename fails with EXDEV optionally move the file to a special directory
and symlink to it.
.IP \[bu] 2
\f[B]posix_acl=BOOL\f[]: Enable POSIX ACL support (if supported by
kernel and underlying filesystem).
(default: false)
@ -542,6 +554,75 @@ On kernels before 4.20 the value has no effect.
The reason the value is configurable is to enable experimentation and
benchmarking.
See the BENCHMARKING section for examples.
.SS follow\-symlinks
.PP
This feature, when enabled, will cause symlinks to be interpreted by
mergerfs as their target (depending on the mode).
.PP
When there is a getattr/stat request for a file mergerfs will check if
the file is a symlink and depending on the \f[C]follow\-symlinks\f[]
setting will replace the information about the symlink with that of that
which it points to.
.PP
When unlink\[aq]ing or rmdir\[aq]ing the followed symlink it will remove
the symlink itself and not that which it points to.
.IP \[bu] 2
never: Behave as normal.
Symlinks are treated as such.
.IP \[bu] 2
directory: Resolve symlinks only which point to directories.
.IP \[bu] 2
regular: Resolve symlinks only which point to regular files.
.IP \[bu] 2
all: Resolve all symlinks to that which they point to.
.PP
Symlinks which do not point to anything are left as is.
.PP
WARNING: This feature works but there might be edge cases yet found.
If you find any odd behaviors please file a ticket on
github (https://github.com/trapexit/mergerfs/issues).
.SS link\-exdev
.PP
If using path preservation and a \f[C]link\f[] fails with EXDEV make a
call to \f[C]symlink\f[] where the \f[C]target\f[] is the
\f[C]oldlink\f[] and the \f[C]linkpath\f[] is the \f[C]newpath\f[].
The \f[C]target\f[] value is determined by the value of
\f[C]link\-exdev\f[].
.IP \[bu] 2
passthrough: Return EXDEV as normal.
.IP \[bu] 2
rel\-symlink: A relative path from the \f[C]newpath\f[].
.IP \[bu] 2
abs\-base\-symlink: A absolute value using the underlying branch.
.IP \[bu] 2
abs\-pool\-symlink: A absolute value using the mergerfs mount point.
.PP
NOTE: It is possible that some applications check the file they link.
In those cases it is possible it will error or complain.
.SS rename\-exdev
.PP
If using path preservation and a \f[C]rename\f[] fails with EXDEV:
.IP "1." 3
Move file from \f[B]/branch/a/b/c\f[] to
\f[B]/branch/.mergerfs_rename_exdev/a/b/c\f[].
.IP "2." 3
symlink the rename\[aq]s \f[C]newpath\f[] to the moved file.
.PP
The \f[C]target\f[] value is determined by the value of
\f[C]rename\-exdev\f[].
.IP \[bu] 2
passthrough: Return EXDEV as normal.
.IP \[bu] 2
rel\-symlink: A relative path from the \f[C]newpath\f[].
.IP \[bu] 2
abs\-symlink: A absolute value using the mergerfs mount point.
.PP
NOTE: It is possible that some applications check the file they rename.
In those cases it is possible it will error or complain.
.PP
NOTE: The reason \f[C]abs\-symlink\f[] is not split into two like
\f[C]link\-exdev\f[] is due to the complexities in managing absolute
base symlinks when multiple \f[C]oldpaths\f[] exist.
.SS symlinkify
.PP
Due to the levels of indirection introduced by mergerfs and the

8
src/config.cpp

@ -71,6 +71,7 @@ namespace l
Config::Config()
: async_read(true),
auto_cache(false),
minfreespace(MINFREESPACE_DEFAULT),
branches(minfreespace),
cache_attr(1),
cache_entry(1),
@ -83,12 +84,13 @@ Config::Config()
direct_io(false),
dropcacheonclose(false),
fsname(),
follow_symlinks(FollowSymlinks::ENUM::NEVER),
func(),
fuse_msg_size(FUSE_MAX_MAX_PAGES),
ignorepponrename(false),
inodecalc("hybrid-hash"),
link_cow(false),
minfreespace(MINFREESPACE_DEFAULT),
link_exdev(LinkEXDEV::ENUM::PASSTHROUGH),
mount(),
moveonenospc(false),
nfsopenhack(NFSOpenHack::ENUM::OFF),
@ -97,6 +99,7 @@ Config::Config()
posix_acl(false),
readdir(ReadDir::ENUM::POSIX),
readdirplus(false),
rename_exdev(RenameEXDEV::ENUM::PASSTHROUGH),
security_capability(true),
srcmounts(branches),
statfs(StatFS::ENUM::BASE),
@ -124,6 +127,7 @@ Config::Config()
_map["category.search"] = &category.search;
_map["direct_io"] = &direct_io;
_map["dropcacheonclose"] = &dropcacheonclose;
_map["follow-symlinks"] = &follow_symlinks;
_map["fsname"] = &fsname;
_map["func.access"] = &func.access;
_map["func.chmod"] = &func.chmod;
@ -150,6 +154,7 @@ Config::Config()
_map["inodecalc"] = &inodecalc;
_map["kernel_cache"] = &kernel_cache;
_map["link_cow"] = &link_cow;
_map["link-exdev"] = &link_exdev;
_map["minfreespace"] = &minfreespace;
_map["mount"] = &mount;
_map["moveonenospc"] = &moveonenospc;
@ -159,6 +164,7 @@ Config::Config()
_map["posix_acl"] = &posix_acl;
// _map["readdir"] = &readdir;
_map["readdirplus"] = &readdirplus;
_map["rename-exdev"] = &rename_exdev;
_map["security_capability"] = &security_capability;
_map["srcmounts"] = &srcmounts;
_map["statfs"] = &statfs;

9
src/config.hpp

@ -19,10 +19,13 @@
#include "branches.hpp"
#include "category.hpp"
#include "config_cachefiles.hpp"
#include "config_follow_symlinks.hpp"
#include "config_inodecalc.hpp"
#include "config_link_exdev.hpp"
#include "config_moveonenospc.hpp"
#include "config_nfsopenhack.hpp"
#include "config_readdir.hpp"
#include "config_rename_exdev.hpp"
#include "config_statfs.hpp"
#include "config_statfsignore.hpp"
#include "config_xattr.hpp"
@ -38,7 +41,6 @@
#include <cstdint>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
@ -98,6 +100,7 @@ public:
public:
ConfigBOOL async_read;
ConfigBOOL auto_cache;
ConfigUINT64 minfreespace;
Branches branches;
ConfigUINT64 cache_attr;
ConfigUINT64 cache_entry;
@ -110,13 +113,14 @@ public:
ConfigBOOL direct_io;
ConfigBOOL dropcacheonclose;
ConfigSTR fsname;
FollowSymlinks follow_symlinks;
Funcs func;
ConfigUINT64 fuse_msg_size;
ConfigBOOL ignorepponrename;
InodeCalc inodecalc;
ConfigBOOL kernel_cache;
ConfigBOOL link_cow;
ConfigUINT64 minfreespace;
LinkEXDEV link_exdev;
ConfigSTR mount;
MoveOnENOSPC moveonenospc;
NFSOpenHack nfsopenhack;
@ -125,6 +129,7 @@ public:
ConfigBOOL posix_acl;
ReadDir readdir;
ConfigBOOL readdirplus;
RenameEXDEV rename_exdev;
ConfigBOOL security_capability;
SrcMounts srcmounts;
StatFS statfs;

58
src/config_follow_symlinks.cpp

@ -0,0 +1,58 @@
/*
ISC License
Copyright (c) 2020, 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.
*/
#include "config_follow_symlinks.hpp"
#include "ef.hpp"
#include "errno.hpp"
template<>
std::string
FollowSymlinks::to_string(void) const
{
switch(_data)
{
case FollowSymlinks::ENUM::NEVER:
return "never";
case FollowSymlinks::ENUM::DIRECTORY:
return "directory";
case FollowSymlinks::ENUM::REGULAR:
return "regular";
case FollowSymlinks::ENUM::ALL:
return "all";
}
return "invalid";
}
template<>
int
FollowSymlinks::from_string(const std::string &s_)
{
if(s_ == "never")
_data = FollowSymlinks::ENUM::NEVER;
ef(s_ == "directory")
_data = FollowSymlinks::ENUM::DIRECTORY;
ef(s_ == "regular")
_data = FollowSymlinks::ENUM::REGULAR;
ef(s_ == "all")
_data = FollowSymlinks::ENUM::ALL;
else
return -EINVAL;
return 0;
}

30
src/config_follow_symlinks.hpp

@ -0,0 +1,30 @@
/*
ISC License
Copyright (c) 2020, 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 "enum.hpp"
enum class FollowSymlinksEnum
{
NEVER,
DIRECTORY,
REGULAR,
ALL
};
typedef Enum<FollowSymlinksEnum> FollowSymlinks;

58
src/config_link_exdev.cpp

@ -0,0 +1,58 @@
/*
ISC License
Copyright (c) 2021, 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.
*/
#include "config_link_exdev.hpp"
#include "ef.hpp"
#include "errno.hpp"
template<>
std::string
LinkEXDEV::to_string(void) const
{
switch(_data)
{
case LinkEXDEV::ENUM::PASSTHROUGH:
return "passthrough";
case LinkEXDEV::ENUM::REL_SYMLINK:
return "rel-symlink";
case LinkEXDEV::ENUM::ABS_BASE_SYMLINK:
return "abs-base-symlink";
case LinkEXDEV::ENUM::ABS_POOL_SYMLINK:
return "abs-pool-symlink";
}
return "invalid";
}
template<>
int
LinkEXDEV::from_string(const std::string &s_)
{
if(s_ == "passthrough")
_data = LinkEXDEV::ENUM::PASSTHROUGH;
ef(s_ == "rel-symlink")
_data = LinkEXDEV::ENUM::REL_SYMLINK;
ef(s_ == "abs-base-symlink")
_data = LinkEXDEV::ENUM::ABS_BASE_SYMLINK;
ef(s_ == "abs-pool-symlink")
_data = LinkEXDEV::ENUM::ABS_POOL_SYMLINK;
else
return -EINVAL;
return 0;
}

30
src/config_link_exdev.hpp

@ -0,0 +1,30 @@
/*
ISC License
Copyright (c) 2021, 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 "enum.hpp"
enum class LinkEXDEVEnum
{
PASSTHROUGH,
REL_SYMLINK,
ABS_BASE_SYMLINK,
ABS_POOL_SYMLINK
};
typedef Enum<LinkEXDEVEnum> LinkEXDEV;

54
src/config_rename_exdev.cpp

@ -0,0 +1,54 @@
/*
ISC License
Copyright (c) 2021, 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.
*/
#include "config_rename_exdev.hpp"
#include "ef.hpp"
#include "errno.hpp"
template<>
std::string
RenameEXDEV::to_string(void) const
{
switch(_data)
{
case RenameEXDEV::ENUM::PASSTHROUGH:
return "passthrough";
case RenameEXDEV::ENUM::REL_SYMLINK:
return "rel-symlink";
case RenameEXDEV::ENUM::ABS_SYMLINK:
return "abs-symlink";
}
return "invalid";
}
template<>
int
RenameEXDEV::from_string(const std::string &s_)
{
if(s_ == "passthrough")
_data = RenameEXDEV::ENUM::PASSTHROUGH;
ef(s_ == "rel-symlink")
_data = RenameEXDEV::ENUM::REL_SYMLINK;
ef(s_ == "abs-symlink")
_data = RenameEXDEV::ENUM::ABS_SYMLINK;
else
return -EINVAL;
return 0;
}

29
src/config_rename_exdev.hpp

@ -0,0 +1,29 @@
/*
ISC License
Copyright (c) 2021, 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 "enum.hpp"
enum class RenameEXDEVEnum
{
PASSTHROUGH,
REL_SYMLINK,
ABS_SYMLINK
};
typedef Enum<RenameEXDEVEnum> RenameEXDEV;

4
src/enum.hpp

@ -67,8 +67,8 @@ public:
}
public:
std::string to_string() const;
int from_string(const std::string &);
std::string to_string() const final;
int from_string(const std::string &) final;
public:
int to_int() const

22
src/fs_mkdir.hpp

@ -18,6 +18,8 @@
#pragma once
#include "ghc/filesystem.hpp"
#include <string>
#include <sys/stat.h>
@ -26,12 +28,30 @@
namespace fs
{
static
inline
int
mkdir(const char *path_,
const mode_t mode_)
{
return ::mkdir(path_,mode_);
}
static
inline
int
mkdir(const std::string &path_,
const mode_t mode_)
{
return ::mkdir(path_.c_str(),mode_);
return fs::mkdir(path_.c_str(),mode_);
}
static
inline
int
mkdir(const ghc::filesystem::path &path_,
const mode_t mode_)
{
return fs::mkdir(path_.c_str(),mode_);
}
}

35
src/fs_mkdir_as_root.hpp

@ -0,0 +1,35 @@
/*
ISC License
Copyright (c) 2021, 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.
*/
#include "fs_mkdir.hpp"
#include "ugid.hpp"
namespace fs
{
template<typename T>
static
inline
int
mkdir_as_root(const T &path_,
const mode_t mode_)
{
const ugid::SetRootGuard guard;
return fs::mkdir(path_,mode_);
}
}

21
src/fs_symlink.hpp

@ -28,30 +28,27 @@ namespace fs
static
inline
int
symlink(const char *oldpath_,
const char *newpath_)
symlink(const char *target_,
const char *linkpath_)
{
return ::symlink(oldpath_,
newpath_);
return ::symlink(target_,linkpath_);
}
static
inline
int
symlink(const std::string &oldpath_,
const std::string &newpath_)
symlink(const std::string &target_,
const std::string &linkpath_)
{
return fs::symlink(oldpath_.c_str(),
newpath_.c_str());
return ::symlink(target_.c_str(),linkpath_.c_str());
}
static
inline
int
symlink(const char *oldpath_,
const std::string &newpath_)
symlink(const char *target_,
const std::string &linkpath_)
{
return fs::symlink(oldpath_,
newpath_.c_str());
return ::symlink(target_,linkpath_.c_str());
}
}

66
src/fuse_getattr.cpp

@ -19,6 +19,7 @@
#include "fs_inode.hpp"
#include "fs_lstat.hpp"
#include "fs_path.hpp"
#include "fs_stat.hpp"
#include "symlinkify.hpp"
#include "ugid.hpp"
@ -31,6 +32,42 @@ using std::string;
namespace l
{
static
void
set_stat_if_leads_to_dir(const std::string &path_,
struct stat *st_)
{
int rv;
struct stat st;
rv = fs::stat(path_,&st);
if(rv == -1)
return;
if(S_ISDIR(st.st_mode))
*st_ = st;
return;
}
static
void
set_stat_if_leads_to_reg(const std::string &path_,
struct stat *st_)
{
int rv;
struct stat st;
rv = fs::stat(path_,&st);
if(rv == -1)
return;
if(S_ISREG(st.st_mode))
*st_ = st;
return;
}
static
int
getattr_controlfile(struct stat *st_)
@ -63,7 +100,8 @@ namespace l
const char *fusepath_,
struct stat *st_,
const bool symlinkify_,
const time_t symlinkify_timeout_)
const time_t symlinkify_timeout_,
FollowSymlinks followsymlinks_)
{
int rv;
string fullpath;
@ -75,7 +113,28 @@ namespace l
fullpath = fs::path::make(basepaths[0],fusepath_);
rv = fs::lstat(fullpath,st_);
switch(followsymlinks_)
{
case FollowSymlinks::ENUM::NEVER:
rv = fs::lstat(fullpath,st_);
break;
case FollowSymlinks::ENUM::DIRECTORY:
rv = fs::lstat(fullpath,st_);
if(S_ISLNK(st_->st_mode))
l::set_stat_if_leads_to_dir(fullpath,st_);
break;
case FollowSymlinks::ENUM::REGULAR:
rv = fs::lstat(fullpath,st_);
if(S_ISLNK(st_->st_mode))
l::set_stat_if_leads_to_reg(fullpath,st_);
break;
case FollowSymlinks::ENUM::ALL:
rv = fs::stat(fullpath,st_);
if(rv != 0)
rv = fs::lstat(fullpath,st_);
break;
}
if(rv == -1)
return -errno;
@ -102,7 +161,8 @@ namespace l
fusepath_,
st_,
cfg->symlinkify,
cfg->symlinkify_timeout);
cfg->symlinkify_timeout,
cfg->follow_symlinks);
timeout_->entry = ((rv >= 0) ?
cfg->cache_entry :

324
src/fuse_link.cpp

@ -18,7 +18,11 @@
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_link.hpp"
#include "fs_lstat.hpp"
#include "fs_path.hpp"
#include "fuse_getattr.hpp"
#include "fuse_symlink.hpp"
#include "ghc/filesystem.hpp"
#include "ugid.hpp"
#include "fuse.h"
@ -28,7 +32,7 @@
using std::string;
using std::vector;
namespace gfs = ghc::filesystem;
namespace error
{
@ -54,45 +58,36 @@ namespace l
{
static
int
link_create_path_core(const string &oldbasepath_,
link_create_path_loop(const StrVec &oldbasepaths_,
const string &newbasepath_,
const char *oldfusepath_,
const char *newfusepath_,
const int error_)
const string &newfusedirpath_,
struct stat *st_)
{
int rv;
int error;
string oldfullpath;
string newfullpath;
oldfullpath = fs::path::make(oldbasepath_,oldfusepath_);
newfullpath = fs::path::make(oldbasepath_,newfusepath_);
error = -1;
for(auto &oldbasepath : oldbasepaths_)
{
oldfullpath = fs::path::make(oldbasepath,oldfusepath_);
newfullpath = fs::path::make(oldbasepath,newfusepath_);
rv = fs::link(oldfullpath,newfullpath);
rv = fs::link(oldfullpath,newfullpath);
if((rv == -1) && (errno == ENOENT))
{
rv = fs::clonepath_as_root(newbasepath_,oldbasepath,newfusedirpath_);
if(rv == 0)
rv = fs::link(oldfullpath,newfullpath);
}
return error::calc(rv,error_,errno);
}
if((rv == 0) && (st_->st_ino == 0))
rv = fs::lstat(oldfullpath,st_);
static
int
link_create_path_loop(const StrVec &oldbasepaths_,
const string &newbasepath_,
const char *oldfusepath_,
const char *newfusepath_,
const string &newfusedirpath_)
{
int rv;
int error;
error = -1;
for(size_t i = 0, ei = oldbasepaths_.size(); i != ei; i++)
{
rv = fs::clonepath_as_root(newbasepath_,oldbasepaths_[i],newfusedirpath_);
if(rv == -1)
error = error::calc(rv,error,errno);
else
error = l::link_create_path_core(oldbasepaths_[i],newbasepath_,
oldfusepath_,newfusepath_,
error);
error = error::calc(rv,error,errno);
}
return -error;
@ -104,7 +99,8 @@ namespace l
const Policy::Action &actionFunc_,
const Branches &branches_,
const char *oldfusepath_,
const char *newfusepath_)
const char *newfusepath_,
struct stat *st_)
{
int rv;
string newfusedirpath;
@ -123,47 +119,17 @@ namespace l
return l::link_create_path_loop(oldbasepaths,newbasepaths[0],
oldfusepath_,newfusepath_,
newfusedirpath);
newfusedirpath,
st_);
}
static
int
clonepath_if_would_create(const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const string &oldbasepath_,
const char *oldfusepath_,
const char *newfusepath_)
{
int rv;
string newfusedirpath;
StrVec newbasepath;
newfusedirpath = fs::path::dirname(newfusepath_);
rv = createFunc_(branches_,newfusedirpath,&newbasepath);
if(rv == -1)
return -1;
if(oldbasepath_ != newbasepath[0])
return (errno=EXDEV,-1);
rv = searchFunc_(branches_,newfusedirpath,&newbasepath);
if(rv == -1)
return -1;
return fs::clonepath_as_root(newbasepath[0],oldbasepath_,newfusedirpath);
}
static
int
link_preserve_path_core(const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const string &oldbasepath_,
const char *oldfusepath_,
const char *newfusepath_,
const int error_)
link_preserve_path_core(const string &oldbasepath_,
const char *oldfusepath_,
const char *newfusepath_,
struct stat *st_,
const int error_)
{
int rv;
string oldfullpath;
@ -174,36 +140,29 @@ namespace l
rv = fs::link(oldfullpath,newfullpath);
if((rv == -1) && (errno == ENOENT))
{
rv = l::clonepath_if_would_create(searchFunc_,createFunc_,
branches_,
oldbasepath_,
oldfusepath_,newfusepath_);
if(rv != -1)
rv = fs::link(oldfullpath,newfullpath);
}
errno = EXDEV;
if((rv == 0) && (st_->st_ino == 0))
rv = fs::lstat(oldfullpath,st_);
return error::calc(rv,error_,errno);
}
static
int
link_preserve_path_loop(const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const char *oldfusepath_,
const char *newfusepath_,
const StrVec &oldbasepaths_)
link_preserve_path_loop(const StrVec &oldbasepaths_,
const char *oldfusepath_,
const char *newfusepath_,
struct stat *st_)
{
int error;
error = -1;
for(size_t i = 0, ei = oldbasepaths_.size(); i != ei; i++)
for(auto &oldbasepath : oldbasepaths_)
{
error = l::link_preserve_path_core(searchFunc_,createFunc_,
branches_,
oldbasepaths_[i],
oldfusepath_,newfusepath_,
error = l::link_preserve_path_core(oldbasepath,
oldfusepath_,
newfusepath_,
st_,
error);
}
@ -212,12 +171,11 @@ namespace l
static
int
link_preserve_path(const Policy::Search &searchFunc_,
const Policy::Action &actionFunc_,
const Policy::Create &createFunc_,
link_preserve_path(const Policy::Action &actionFunc_,
const Branches &branches_,
const char *oldfusepath_,
const char *newfusepath_)
const char *newfusepath_,
struct stat *st_)
{
int rv;
StrVec oldbasepaths;
@ -226,35 +184,183 @@ namespace l
if(rv == -1)
return -errno;
return l::link_preserve_path_loop(searchFunc_,createFunc_,
branches_,
oldfusepath_,newfusepath_,
oldbasepaths);
return l::link_preserve_path_loop(oldbasepaths,
oldfusepath_,
newfusepath_,
st_);
}
static
int
link(Config::Read &cfg_,
const char *oldpath_,
const char *newpath_,
struct stat *st_)
{
if(cfg_->func.create.policy.path_preserving() && !cfg_->ignorepponrename)
return l::link_preserve_path(cfg_->func.link.policy,
cfg_->branches,
oldpath_,
newpath_,
st_);
return l::link_create_path(cfg_->func.getattr.policy,
cfg_->func.link.policy,
cfg_->branches,
oldpath_,
newpath_,
st_);
}
static
int
link(Config::Read &cfg_,
const char *oldpath_,
const char *newpath_,
struct stat *st_,
fuse_timeouts_t *timeouts_)
{
int rv;
rv = l::link(cfg_,oldpath_,newpath_,st_);
timeouts_->entry = ((rv >= 0) ?
cfg_->cache_entry :
cfg_->cache_negative_entry);
timeouts_->attr = cfg_->cache_attr;
return rv;
}
static
int
link_exdev_rel_symlink(const char *oldpath_,
const char *newpath_,
struct stat *st_,
fuse_timeouts_t *timeouts_)
{
int rv;
gfs::path target(oldpath_);
gfs::path linkpath(newpath_);
target = target.lexically_relative(linkpath.parent_path());
rv = FUSE::symlink(target.c_str(),linkpath.c_str());
if(rv == 0)
rv = FUSE::getattr(oldpath_,st_,timeouts_);
// Disable attr caching since we created a symlink but should be a regular.
timeouts_->attr = 0;
return rv;
}
static
int
link_exdev_abs_base_symlink(const Policy::Search &openPolicy_,
const Branches::CPtr &branches_,
const char *oldpath_,
const char *newpath_,
struct stat *st_,
fuse_timeouts_t *timeouts_)
{
int rv;
StrVec basepaths;
std::string target;
rv = openPolicy_(branches_,oldpath_,&basepaths);
if(rv == -1)
return -errno;
target = fs::path::make(basepaths[0],oldpath_);
rv = FUSE::symlink(target.c_str(),newpath_);
if(rv == 0)
rv = FUSE::getattr(oldpath_,st_,timeouts_);
// Disable attr caching since we created a symlink but should be a regular.
timeouts_->attr = 0;
return rv;
}
static
int
link_exdev_abs_pool_symlink(const std::string &mount_,
const char *oldpath_,
const char *newpath_,
struct stat *st_,
fuse_timeouts_t *timeouts_)
{
int rv;
StrVec basepaths;
std::string target;
target = fs::path::make(mount_,oldpath_);
rv = FUSE::symlink(target.c_str(),newpath_);
if(rv == 0)
rv = FUSE::getattr(oldpath_,st_,timeouts_);
// Disable attr caching since we created a symlink but should be a regular.
timeouts_->attr = 0;
return rv;
}
static
int
link_exdev(Config::Read &cfg_,
const char *oldpath_,
const char *newpath_,
struct stat *st_,
fuse_timeouts_t *timeouts_)
{
switch(cfg_->link_exdev)
{
case LinkEXDEV::ENUM::PASSTHROUGH:
return -EXDEV;
case LinkEXDEV::ENUM::REL_SYMLINK:
return l::link_exdev_rel_symlink(oldpath_,
newpath_,
st_,
timeouts_);
case LinkEXDEV::ENUM::ABS_BASE_SYMLINK:
return l::link_exdev_abs_base_symlink(cfg_->func.open.policy,
cfg_->branches,
oldpath_,
newpath_,
st_,
timeouts_);
case LinkEXDEV::ENUM::ABS_POOL_SYMLINK:
return l::link_exdev_abs_pool_symlink(cfg_->mount,
oldpath_,
newpath_,
st_,
timeouts_);
}
return -EXDEV;
}
}
namespace FUSE
{
int
link(const char *from_,
const char *to_)
link(const char *oldpath_,
const char *newpath_,
struct stat *st_,
fuse_timeouts_t *timeouts_)
{
int rv;
Config::Read cfg;
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
if(cfg->func.create.policy.path_preserving() && !cfg->ignorepponrename)
return l::link_preserve_path(cfg->func.getattr.policy,
cfg->func.link.policy,
cfg->func.create.policy,
cfg->branches,
from_,
to_);
return l::link_create_path(cfg->func.getattr.policy,
cfg->func.link.policy,
cfg->branches,
from_,
to_);
rv = l::link(cfg,oldpath_,newpath_,st_,timeouts_);
if(rv == -EXDEV)
return l::link_exdev(cfg,oldpath_,newpath_,st_,timeouts_);
return rv;
}
}

8
src/fuse_link.hpp

@ -16,10 +16,14 @@
#pragma once
#include "fuse.h"
namespace FUSE
{
int
link(const char *from,
const char *to);
link(const char *oldpath,
const char *newpath,
struct stat *st,
fuse_timeouts_t *timeouts);
}

544
src/fuse_rename.cpp

@ -17,16 +17,26 @@
#include "config.hpp"
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_link.hpp"
#include "fs_mkdir_as_root.hpp"
#include "fs_path.hpp"
#include "fs_remove.hpp"
#include "fs_rename.hpp"
#include "fs_symlink.hpp"
#include "fs_unlink.hpp"
#include "fuse_symlink.hpp"
#include "ugid.hpp"
#include "ghc/filesystem.hpp"
#include <algorithm>
#include <string>
#include <vector>
using std::string;
#include <iostream>
using std::string;
namespace gfs = ghc::filesystem;
namespace error
{
@ -48,265 +58,351 @@ namespace error
}
}
static
bool
member(const StrVec &haystack,
const string &needle)
namespace l
{
for(size_t i = 0, ei = haystack.size(); i != ei; i++)
{
if(haystack[i] == needle)
return true;
}
static
bool
contains(const StrVec &haystack_,
const char *needle_)
{
for(auto &hay : haystack_)
{
if(hay == needle_)
return true;
}
return false;
}
return false;
}
static
void
_remove(const StrVec &toremove)
{
for(size_t i = 0, ei = toremove.size(); i != ei; i++)
fs::remove(toremove[i]);
}
static
bool
contains(const StrVec &haystack_,
const string &needle_)
{
return l::contains(haystack_,needle_.c_str());
}
static
void
_rename_create_path_core(const StrVec &oldbasepaths,
const string &oldbasepath,
const string &newbasepath,
const char *oldfusepath,
const char *newfusepath,
const string &newfusedirpath,
int &error,
StrVec &tounlink)
{
int rv;
bool ismember;
string oldfullpath;
string newfullpath;
ismember = member(oldbasepaths,oldbasepath);
if(ismember)
{
rv = fs::clonepath_as_root(newbasepath,oldbasepath,newfusedirpath);
if(rv != -1)
{
oldfullpath = fs::path::make(oldbasepath,oldfusepath);
newfullpath = fs::path::make(oldbasepath,newfusepath);
rv = fs::rename(oldfullpath,newfullpath);
}
error = error::calc(rv,error,errno);
if(rv == -1)
tounlink.push_back(oldfullpath);
}
else
{
newfullpath = fs::path::make(oldbasepath,newfusepath);
tounlink.push_back(newfullpath);
}
}
static
void
remove(const StrVec &toremove_)
{
for(auto &path : toremove_)
fs::remove(path);
}
static
int
_rename_create_path(const Policy::Search &searchFunc,
const Policy::Action &actionFunc,
const Branches::CPtr &branches_,
const char *oldfusepath,
const char *newfusepath)
{
int rv;
int error;
string newfusedirpath;
StrVec toremove;
StrVec newbasepath;
StrVec oldbasepaths;
StrVec branches;
static
void
remove(const Branches::CPtr &branches_,
const std::string &relpath_)
{
std::string fullpath;
rv = actionFunc(branches_,oldfusepath,&oldbasepaths);
if(rv == -1)
return -errno;
for(auto &branch : *branches_)
{
fullpath = fs::path::make(branch.path,relpath_);
fs::remove(fullpath);
}
}
newfusedirpath = fs::path::dirname(newfusepath);
static
int
rename_create_path(const Policy::Search &searchPolicy_,
const Policy::Action &actionPolicy_,
const Branches::CPtr &branches_,
const gfs::path &oldfusepath_,
const gfs::path &newfusepath_)
{
int rv;
int error;
StrVec toremove;
StrVec newbasepath;
StrVec oldbasepaths;
gfs::path oldfullpath;
gfs::path newfullpath;
rv = actionPolicy_(branches_,oldfusepath_,&oldbasepaths);
if(rv == -1)
return -errno;
rv = searchPolicy_(branches_,newfusepath_.parent_path(),&newbasepath);
if(rv == -1)
return -errno;
error = -1;
for(auto &branch : *branches_)
{
newfullpath = branch.path;
newfullpath += newfusepath_;
if(!l::contains(oldbasepaths,branch.path))
{
toremove.push_back(newfullpath);
continue;
}
oldfullpath = branch.path;
oldfullpath += oldfusepath_;
rv = fs::rename(oldfullpath,newfullpath);
if(rv == -1)
{
rv = fs::clonepath_as_root(newbasepath[0],branch.path,newfusepath_.parent_path());
if(rv == 0)
rv = fs::rename(oldfullpath,newfullpath);
}
error = error::calc(rv,error,errno);
if(rv == -1)
toremove.push_back(oldfullpath);
}
rv = searchFunc(branches_,newfusedirpath.c_str(),&newbasepath);
if(rv == -1)
return -errno;
if(error == 0)
l::remove(toremove);
branches_->to_paths(branches);
return -error;
}
error = -1;
for(size_t i = 0, ei = branches.size(); i != ei; i++)
{
const string &oldbasepath = branches[i];
static
int
rename_preserve_path(const Policy::Action &actionPolicy_,
const Branches::CPtr &branches_,
const gfs::path &oldfusepath_,
const gfs::path &newfusepath_)
{
int rv;
bool success;
StrVec toremove;
StrVec oldbasepaths;
gfs::path oldfullpath;
gfs::path newfullpath;
rv = actionPolicy_(branches_,oldfusepath_,&oldbasepaths);
if(rv == -1)
return -errno;
success = false;
for(auto &branch : *branches_)
{
newfullpath = branch.path;
newfullpath += newfusepath_;
if(!l::contains(oldbasepaths,branch.path))
{
toremove.push_back(newfullpath);
continue;
}
oldfullpath = branch.path;
oldfullpath += oldfusepath_;
rv = fs::rename(oldfullpath,newfullpath);
if(rv == -1)
{
toremove.push_back(oldfullpath);
continue;
}
success = true;
}
_rename_create_path_core(oldbasepaths,
oldbasepath,newbasepath[0],
oldfusepath,newfusepath,
newfusedirpath,
error,toremove);
}
// TODO: probably should try to be nuanced here.
if(success == false)
return -EXDEV;
l::remove(toremove);
if(error == 0)
_remove(toremove);
return 0;
}
return -error;
}
static
void
rename_exdev_rename_back(const StrVec &basepaths_,
const gfs::path &oldfusepath_)
{
gfs::path oldpath;
gfs::path newpath;
static
int
_clonepath(const Policy::Search &searchFunc,
const Branches::CPtr &branches_,
const string &dstbasepath,
const string &fusedirpath)
{
int rv;
StrVec srcbasepath;
for(auto &basepath : basepaths_)
{
oldpath = basepath;
oldpath /= ".mergerfs_rename_exdev";
oldpath += oldfusepath_;
rv = searchFunc(branches_,fusedirpath.c_str(),&srcbasepath);
if(rv == -1)
return -errno;
newpath = basepath;
newpath += oldfusepath_;
fs::clonepath_as_root(srcbasepath[0],dstbasepath,fusedirpath);
fs::rename(oldpath,newpath);
}
}
return 0;
}
static
int
rename_exdev_rename_target(const Policy::Action &actionPolicy_,
const Branches::CPtr &branches_,
const gfs::path &oldfusepath_,
StrVec *basepaths_)
{
int rv;
gfs::path clonesrc;
gfs::path clonetgt;
rv = actionPolicy_(branches_,oldfusepath_,basepaths_);
if(rv == -1)
return -errno;
ugid::SetRootGuard ugidGuard;
for(auto &basepath : *basepaths_)
{
clonesrc = basepath;
clonetgt = basepath;
clonetgt /= ".mergerfs_rename_exdev";
rv = fs::clonepath(clonesrc,clonetgt,oldfusepath_.parent_path());
if((rv == -1) && (errno == ENOENT))
{
fs::mkdir(clonetgt,01777);
rv = fs::clonepath(clonesrc,clonetgt,oldfusepath_.parent_path());
}
if(rv == -1)
goto error;
clonesrc += oldfusepath_;
clonetgt += oldfusepath_;
rv = fs::rename(clonesrc,clonetgt);
if(rv == -1)
goto error;
}
return 0;
error:
l::rename_exdev_rename_back(*basepaths_,oldfusepath_);
return -EXDEV;
}
static
int
_clonepath_if_would_create(const Policy::Search &searchFunc,
const Policy::Create &createFunc,
static
int
rename_exdev_rel_symlink(const Policy::Action &actionPolicy_,
const Branches::CPtr &branches_,
const string &oldbasepath,
const char *oldfusepath,
const char *newfusepath)
{
int rv;
string newfusedirpath;
StrVec newbasepath;
const gfs::path &oldfusepath_,
const gfs::path &newfusepath_)
{
int rv;
StrVec basepaths;
gfs::path target;
gfs::path linkpath;
newfusedirpath = fs::path::dirname(newfusepath);
rv = l::rename_exdev_rename_target(actionPolicy_,branches_,oldfusepath_,&basepaths);
if(rv < 0)
return rv;
rv = createFunc(branches_,newfusedirpath.c_str(),&newbasepath);
if(rv == -1)
return rv;
linkpath = newfusepath_;
target = "/.mergerfs_rename_exdev";
target += oldfusepath_;
target = target.lexically_relative(linkpath.parent_path());
if(oldbasepath == newbasepath[0])
return _clonepath(searchFunc,branches_,oldbasepath,newfusedirpath);
rv = FUSE::symlink(target.c_str(),linkpath.c_str());
if(rv < 0)
l::rename_exdev_rename_back(basepaths,oldfusepath_);
return (errno=EXDEV,-1);
}
return rv;
}
static
void
_rename_preserve_path_core(const Policy::Search &searchFunc,
const Policy::Create &createFunc,
static
int
rename_exdev_abs_symlink(const Policy::Action &actionPolicy_,
const Branches::CPtr &branches_,
const StrVec &oldbasepaths,
const string &oldbasepath,
const char *oldfusepath,
const char *newfusepath,
int &error,
StrVec &toremove)
{
int rv;
bool ismember;
string newfullpath;
newfullpath = fs::path::make(oldbasepath,newfusepath);
ismember = member(oldbasepaths,oldbasepath);
if(ismember)
{
string oldfullpath;
oldfullpath = fs::path::make(oldbasepath,oldfusepath);
rv = fs::rename(oldfullpath,newfullpath);
if((rv == -1) && (errno == ENOENT))
{
rv = _clonepath_if_would_create(searchFunc,createFunc,
branches_,oldbasepath,
oldfusepath,newfusepath);
if(rv == 0)
rv = fs::rename(oldfullpath,newfullpath);
}
error = error::calc(rv,error,errno);
if(rv == -1)
toremove.push_back(oldfullpath);
}
else
{
toremove.push_back(newfullpath);
}
}
const std::string &mount_,
const gfs::path &oldfusepath_,
const gfs::path &newfusepath_)
{
int rv;
StrVec basepaths;
gfs::path target;
gfs::path linkpath;
static
int
_rename_preserve_path(const Policy::Search &searchFunc,
const Policy::Action &actionFunc,
const Policy::Create &createFunc,
const Branches::CPtr &branches_,
const char *oldfusepath,
const char *newfusepath)
{
int rv;
int error;
StrVec toremove;
StrVec oldbasepaths;
StrVec branches;
rv = actionFunc(branches_,oldfusepath,&oldbasepaths);
if(rv == -1)
return -errno;
branches_->to_paths(branches);
error = -1;
for(size_t i = 0, ei = branches.size(); i != ei; i++)
{
const string &oldbasepath = branches[i];
_rename_preserve_path_core(searchFunc,createFunc,
branches_,
oldbasepaths,oldbasepath,
oldfusepath,newfusepath,
error,toremove);
}
if(error == 0)
_remove(toremove);
return -error;
rv = l::rename_exdev_rename_target(actionPolicy_,branches_,oldfusepath_,&basepaths);
if(rv < 0)
return rv;
linkpath = newfusepath_;
target = mount_;
target /= ".mergerfs_rename_exdev";
target += oldfusepath_;
rv = FUSE::symlink(target.c_str(),linkpath.c_str());
if(rv < 0)
l::rename_exdev_rename_back(basepaths,oldfusepath_);
return rv;
}
static
int
rename_exdev(Config::Read &cfg_,
const gfs::path &oldfusepath_,
const gfs::path &newfusepath_)
{
switch(cfg_->rename_exdev)
{
case RenameEXDEV::ENUM::PASSTHROUGH:
return -EXDEV;
case RenameEXDEV::ENUM::REL_SYMLINK:
return l::rename_exdev_rel_symlink(cfg_->func.rename.policy,
cfg_->branches,
oldfusepath_,
newfusepath_);
case RenameEXDEV::ENUM::ABS_SYMLINK:
return l::rename_exdev_abs_symlink(cfg_->func.rename.policy,
cfg_->branches,
cfg_->mount,
oldfusepath_,
newfusepath_);
}
return -EXDEV;
}
static
int
rename(Config::Read &cfg_,
const gfs::path &oldpath_,
const gfs::path &newpath_)
{
if(cfg_->func.create.policy.path_preserving() && !cfg_->ignorepponrename)
return l::rename_preserve_path(cfg_->func.rename.policy,
cfg_->branches,
oldpath_,
newpath_);
return l::rename_create_path(cfg_->func.getattr.policy,
cfg_->func.rename.policy,
cfg_->branches,
oldpath_,
newpath_);
}
}
namespace FUSE
{
int
rename(const char *oldpath,
const char *newpath)
rename(const char *oldfusepath_,
const char *newfusepath_)
{
int rv;
Config::Read cfg;
gfs::path oldfusepath(oldfusepath_);
gfs::path newfusepath(newfusepath_);
const fuse_context *fc = fuse_get_context();
const ugid::Set ugid(fc->uid,fc->gid);
if(cfg->func.create.policy.path_preserving() && !cfg->ignorepponrename)
return _rename_preserve_path(cfg->func.getattr.policy,
cfg->func.rename.policy,
cfg->func.create.policy,
cfg->branches,
oldpath,
newpath);
return _rename_create_path(cfg->func.getattr.policy,
cfg->func.rename.policy,
cfg->branches,
oldpath,
newpath);
rv = l::rename(cfg,oldfusepath,newfusepath);
if(rv == -EXDEV)
return l::rename_exdev(cfg,oldfusepath,newfusepath);
return rv;
}
}

34
src/fuse_rmdir.cpp

@ -16,8 +16,9 @@
#include "config.hpp"
#include "errno.hpp"
#include "fs_rmdir.hpp"
#include "fs_path.hpp"
#include "fs_rmdir.hpp"
#include "fs_unlink.hpp"
#include "ugid.hpp"
#include "fuse.h"
@ -50,9 +51,21 @@ namespace l
{
static
int
rmdir_loop_core(const string &basepath_,
const char *fusepath_,
const int error_)
should_unlink(int rv_,
int errno_,
FollowSymlinks followsymlinks_)
{
return ((rv_ == -1) &&
(errno_ == ENOTDIR) &&
(followsymlinks_ != FollowSymlinks::ENUM::NEVER));
}
static
int
rmdir_core(const string &basepath_,
const char *fusepath_,
const FollowSymlinks followsymlinks_,
const int error_)
{
int rv;
string fullpath;
@ -60,21 +73,24 @@ namespace l
fullpath = fs::path::make(basepath_,fusepath_);
rv = fs::rmdir(fullpath);
if(l::should_unlink(rv,errno,followsymlinks_))
rv = fs::unlink(fullpath);
return error::calc(rv,error_,errno);
}
static
int
rmdir_loop(const StrVec &basepaths_,
const char *fusepath_)
rmdir_loop(const StrVec &basepaths_,
const char *fusepath_,
const FollowSymlinks followsymlinks_)
{
int error;
error = 0;
for(size_t i = 0, ei = basepaths_.size(); i != ei; i++)
{
error = l::rmdir_loop_core(basepaths_[i],fusepath_,error);
error = l::rmdir_core(basepaths_[i],fusepath_,followsymlinks_,error);
}
return -error;
@ -84,6 +100,7 @@ namespace l
int
rmdir(const Policy::Action &actionFunc_,
const Branches &branches_,
const FollowSymlinks followsymlinks_,
const char *fusepath_)
{
int rv;
@ -93,7 +110,7 @@ namespace l
if(rv == -1)
return -errno;
return l::rmdir_loop(basepaths,fusepath_);
return l::rmdir_loop(basepaths,fusepath_,followsymlinks_);
}
}
@ -108,6 +125,7 @@ namespace FUSE
return l::rmdir(cfg->func.rmdir.policy,
cfg->branches,
cfg->follow_symlinks,
fusepath_);
}
}

50
src/fuse_symlink.cpp

@ -16,9 +16,10 @@
#include "config.hpp"
#include "errno.hpp"
#include "fs_symlink.hpp"
#include "fs_clonepath.hpp"
#include "fs_path.hpp"
#include "fs_symlink.hpp"
#include "fuse_getattr.hpp"
#include "ugid.hpp"
#include "fuse.h"
@ -56,16 +57,16 @@ namespace l
static
int
symlink_loop_core(const string &newbasepath_,
const char *oldpath_,
const char *newpath_,
const char *target_,
const char *linkpath_,
const int error_)
{
int rv;
string fullnewpath;
fullnewpath = fs::path::make(newbasepath_,newpath_);
fullnewpath = fs::path::make(newbasepath_,linkpath_);
rv = fs::symlink(oldpath_,fullnewpath);
rv = fs::symlink(target_,fullnewpath);
return error::calc(rv,error_,errno);
}
@ -74,8 +75,8 @@ namespace l
int
symlink_loop(const string &existingpath_,
const StrVec &newbasepaths_,
const char *oldpath_,
const char *newpath_,
const char *target_,
const char *linkpath_,
const string &newdirpath_)
{
int rv;
@ -89,8 +90,8 @@ namespace l
error = error::calc(rv,error,errno);
else
error = l::symlink_loop_core(newbasepaths_[i],
oldpath_,
newpath_,
target_,
linkpath_,
error);
}
@ -102,15 +103,15 @@ namespace l
symlink(const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const char *oldpath_,
const char *newpath_)
const char *target_,
const char *linkpath_)
{
int rv;
string newdirpath;
StrVec newbasepaths;
StrVec existingpaths;
newdirpath = fs::path::dirname(newpath_);
newdirpath = fs::path::dirname(linkpath_);
rv = searchFunc_(branches_,newdirpath,&existingpaths);
if(rv == -1)
@ -121,15 +122,15 @@ namespace l
return -errno;
return l::symlink_loop(existingpaths[0],newbasepaths,
oldpath_,newpath_,newdirpath);
target_,linkpath_,newdirpath);
}
}
namespace FUSE
{
int
symlink(const char *oldpath_,
const char *newpath_)
symlink(const char *target_,
const char *linkpath_)
{
Config::Read cfg;
const fuse_context *fc = fuse_get_context();
@ -138,7 +139,22 @@ namespace FUSE
return l::symlink(cfg->func.getattr.policy,
cfg->func.symlink.policy,
cfg->branches,
oldpath_,
newpath_);
target_,
linkpath_);
}
int
symlink(const char *target_,
const char *linkpath_,
struct stat *st_,
fuse_timeouts_t *timeout_)
{
int rv;
rv = FUSE::symlink(target_,linkpath_);
if(rv < 0)
return rv;
return FUSE::getattr(target_,st_,timeout_);
}
}

15
src/fuse_symlink.hpp

@ -1,5 +1,5 @@
/*
Copyright (c) 2016, Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2020, 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
@ -16,10 +16,19 @@
#pragma once
#include "fuse.h"
#include <sys/stat.h>
namespace FUSE
{
int
symlink(const char *oldpath,
const char *newpath);
symlink(const char *target,
const char *linkpath);
int
symlink(const char *target,
const char *linkpath,
struct stat *st,
fuse_timeouts_t *timeouts);
}

5742
src/ghc/filesystem.hpp
File diff suppressed because it is too large
View File

Loading…
Cancel
Save