Browse Source

Rework credential handling to support chroot & idmap

Run in an elevated credential mode (root) and let the kernel manage
entitlements. Will result in slightly different behavior but should
not be noticed by most.
pull/1595/head
Antonio SJ Musumeci 3 weeks ago
parent
commit
e3f88f3974
  1. 1
      Makefile
  2. 19
      libfuse/lib/fuse_lowlevel.cpp
  3. 9
      mkdocs/docs/config/options.md
  4. 44
      mkdocs/docs/faq/compatibility_and_integration.md
  5. 56
      mkdocs/docs/faq/recommendations_and_warnings.md
  6. 88
      mkdocs/docs/faq/technical_behavior_and_limitations.md
  7. 21
      mkdocs/docs/faq/usage_and_functionality.md
  8. 35
      mkdocs/docs/known_issues_bugs.md
  9. 1
      mkdocs/docs/resource_usage.md
  10. 2
      mkdocs/docs/runtime_interface.md
  11. 1
      mkdocs/mkdocs.yml
  12. 104
      src/caps.cpp
  13. 6
      src/caps.hpp
  14. 8
      src/config.cpp
  15. 3
      src/config.hpp
  16. 65
      src/config_gidcache.cpp
  17. 25
      src/config_gidcache.hpp
  18. 4
      src/fs_attr_linux.icpp
  19. 75
      src/fs_clonepath.cpp
  20. 5
      src/fs_clonepath.hpp
  21. 2
      src/fs_is_rofs.hpp
  22. 49
      src/fs_mkdir_as.hpp
  23. 35
      src/fs_mkdir_as_root.hpp
  24. 48
      src/fs_mkdirat.hpp
  25. 49
      src/fs_mknod_as.hpp
  26. 2
      src/fs_movefile_and_open.cpp
  27. 56
      src/fs_open_as.hpp
  28. 49
      src/fs_symlink_as.hpp
  29. 3
      src/fuse_access.cpp
  30. 1
      src/fuse_chmod.cpp
  31. 3
      src/fuse_chown.cpp
  32. 33
      src/fuse_create.cpp
  33. 2
      src/fuse_getattr.cpp
  34. 2
      src/fuse_getxattr.cpp
  35. 1
      src/fuse_init.cpp
  36. 7
      src/fuse_ioctl.cpp
  37. 9
      src/fuse_link.cpp
  38. 2
      src/fuse_listxattr.cpp
  39. 39
      src/fuse_mkdir.cpp
  40. 39
      src/fuse_mknod.cpp
  41. 2
      src/fuse_open.cpp
  42. 4
      src/fuse_passthrough.hpp
  43. 12
      src/fuse_readdir_cor.cpp
  44. 10
      src/fuse_readdir_cosr.cpp
  45. 7
      src/fuse_readdir_cosr_getdents.icpp
  46. 7
      src/fuse_readdir_cosr_readdir.icpp
  47. 3
      src/fuse_readdir_seq.cpp
  48. 3
      src/fuse_readlink.cpp
  49. 2
      src/fuse_removexattr.cpp
  50. 12
      src/fuse_rename.cpp
  51. 1
      src/fuse_rmdir.cpp
  52. 7
      src/fuse_setxattr.cpp
  53. 1
      src/fuse_statfs.cpp
  54. 4
      src/fuse_statx_supported.icpp
  55. 64
      src/fuse_symlink.cpp
  56. 3
      src/fuse_truncate.cpp
  57. 3
      src/fuse_unlink.cpp
  58. 3
      src/fuse_utimens.cpp
  59. 181
      src/gidcache.cpp
  60. 50
      src/gidcache.hpp
  61. 11
      src/mergerfs.cpp
  62. 2
      src/policy_ff.cpp
  63. 4
      src/predictability.h
  64. 33
      src/ugid.cpp
  65. 111
      src/ugid.hpp
  66. 128
      src/ugid_linux.hpp
  67. 34
      src/ugid_linux.icpp
  68. 106
      src/ugid_rwlock.hpp
  69. 45
      src/ugid_rwlock.icpp

1
Makefile

@ -65,7 +65,6 @@ OPT_FLAGS := -O0 \
$(SANITIZE) \
-fstack-protector-strong \
-Wextra \
-Werror \
-Wno-unused-parameter \
-DDEBUG
endif

19
libfuse/lib/fuse_lowlevel.cpp

@ -1737,14 +1737,14 @@ fuse_send_enomem(struct fuse_ll *f_,
fuse_send_errno(f_,ch_,ENOMEM,unique_id_);
}
static
void
fuse_send_einval(struct fuse_ll *f_,
struct fuse_chan *ch_,
const uint64_t unique_id_)
{
fuse_send_errno(f_,ch_,EINVAL,unique_id_);
}
// static
// void
// fuse_send_einval(struct fuse_ll *f_,
// struct fuse_chan *ch_,
// const uint64_t unique_id_)
// {
// fuse_send_errno(f_,ch_,EINVAL,unique_id_);
// }
static
int
@ -1777,9 +1777,6 @@ fuse_ll_buf_process_read(struct fuse_session *se_,
in = (struct fuse_in_header*)msgbuf_->mem;
if((in->uid == FUSE_INVALID_UIDGID) || (in->gid == FUSE_INVALID_UIDGID))
return fuse_send_einval(se_->f,se_->ch,in->unique);
req = fuse_ll_alloc_req(se_->f);
if(req == NULL)
return fuse_send_enomem(se_->f,se_->ch,in->unique);

9
mkdocs/docs/config/options.md

@ -213,17 +213,14 @@ config file.
* **passthrough.max-stack-depth=INT**: Set to `1` another filesystem
can be stacked on mergerfs. Set to `2` to have mergerfs stacked over
another filesystem. (default: 1)
* **gid-cache.expire-timeout=INT**: Number of seconds till
supplemental group data is refreshed in the [GID
cache](../known_issues_bugs.md#supplemental-user-groups). (default:
3600)
* **gid-cache.remove-timeout=INT**: Number of seconds to wait till
cached data is removed due to lack of usage. (default: 43200)
* **remember-nodes=INT**: The number of seconds to keep the internal
representation of a file once the OS tells mergerfs it is no longer
needed. Really only needed for [exporting mergerfs via
NFS](../remote_filesystems.md) (default: 0)
* **noforget**: Effectively sets `remember-nodes` to infinity.
* **allow-idmap=BOOL**: Enables idmap (identity mapping). (default:
true)
**NOTE:** Options are evaluated in the order listed so if the options
are **func.rmdir=rand,category.action=ff** the **action** category

44
mkdocs/docs/faq/compatibility_and_integration.md

@ -5,16 +5,10 @@
[Primarily Linux.](../setup/installation.md) FreeBSD is casually
supported but not well tested.
With FreeBSD certain Linux functions and FUSE features are not
supported. In many cases the absense will not be noticed however
performance may be impacted due to a core mergerfs design decision
relying on a Linux feature.
Linux allows individual threads to change credentials whereas [FreeBSD
does not](https://wiki.freebsd.org/Per-Thread%20Credentials). As a
result mergerfs must use a lock to ensure critical sections which need
to change credentials are safeguarded. This will limit throughput on
systems where requests to mergerfs come from multiple user identities.
With FreeBSD certain Linux functions and FUSE features [are not
supported.](../known_issues_bugs.md#freebsd-version) In many cases the
absence will not be noticed however more advanced features will not be
available.
### Why not support MacOS?
@ -36,7 +30,7 @@ level API.
Windows, while used for NAS systems more often than MacOS, is still
relatively uncommon when compared to Linux. [Drive
Pool](../project_comparisons.md#stablebits-drivepool) is a reasonable
Pool](../project_comparisons.md#stablebits-drivepool) is a good
alternative.
@ -44,21 +38,31 @@ alternative.
ext4, btrfs, xfs, f2fs, zfs, nfs, etc.
On the surface any filesystem should work but there could be issues
with non-POSIX compliant filesystems such as vfat, ntfs, cifs, exfat,
Most any filesystem should work but there could be issues with
non-POSIX compliant filesystems such as vfat, ntfs, cifs, exfat,
etc. When directories need to be created or files moved by mergerfs if
the filesystem returns errors due to not supporting certain POSIX
filesystem features it could result in the core functions failing.
Since mergerfs is not generally used with non-POSIX filesystems this
has not been a problem for users and there are some checks for known
edgecases but it is possible some are not accounted for. If use with a
edge cases but it is possible some are not accounted for. If use with a
filesystem results in issues please [file a
ticket](https://github.com/trapexit/mergerfs/issues) with the details.
## Can I use mergerfs without SnapRAID? SnapRAID without mergerfs?
[https://www.snapraid.it](https://www.snapraid.it)
Yes. They are completely unrelated pieces of software that just happen
to work well together.
## Can I use mergerfs without nonraid? nonraid without mergerfs?
[https://github.com/qvr/nonraid](https://github.com/qvr/nonraid)
Yes. They are completely unrelated pieces of software that just happen
to work well together.
@ -80,16 +84,8 @@ pool.
## Can mergerfs run via Docker, Podman, Kubernetes, etc.
Yes. With Docker you'll need to include `--cap-add=SYS_ADMIN
--device=/dev/fuse --security-opt=apparmor:unconfined` or similar with
other container runtimes. You should also be running it as root or
given sufficient caps to allow mergerfs to change user and group
identity as well as have root like filesystem permissions. This
ability is critical to how mergerfs works.
Also, as mentioned by [hotio](https://hotio.dev/containers/mergerfs),
with Docker you should probably be mounting with `bind-propagation`
set to `slave`.
Yes. [See installation
page.](../setup/installation.md##podman-docker-oci-containers)
## How does mergerfs interact with user namespaces?

56
mkdocs/docs/faq/recommendations_and_warnings.md

@ -1,56 +0,0 @@
# Recommendations and Warnings
## What should mergerfs NOT be used for?
* Situations where you need large amounts of contiguous space beyond
that available on any singular device. Such as putting 10GiB file on
2 6GiB filesystems.
* databases: Even if the database stored data in separate files
(mergerfs wouldn't offer much otherwise) the higher latency of the
indirection will really harm performance. If it is a lightly used
sqlite3 database then it should be fine.
* VM images: For the same reasons as databases. VM images are accessed
very aggressively and mergerfs will introduce a lot of extra latency.
* As replacement for RAID: mergerfs is just for pooling branches. If
you need device performance aggregation or high availability you
should stick with RAID. However, it is fine to put a filesystem
which is on a RAID setup in mergerfs.
**However, if using [passthrough](../config/passthrough.md) the
performance related issues above are less likely to be a concern. Best
to do testing for your specific use case.**
## It's mentioned that there are some security issues with mhddfs. What are they? How does mergerfs address them?
[mhddfs](https://github.com/trapexit/mhddfs) manages running as
`root` by calling
[getuid()](https://github.com/trapexit/mhddfs/blob/cae96e6251dd91e2bdc24800b4a18a74044f6672/src/main.c#L319)
and if it returns `0` then it will
[chown](http://linux.die.net/man/1/chown) the file. Not only is that a
race condition but it doesn't handle other situations. Rather than
attempting to simulate POSIX ACL behavior the proper way to manage
this is to use [seteuid](http://linux.die.net/man/2/seteuid) and
[setegid](http://linux.die.net/man/2/setegid), in effect, becoming the
user making the original call, and perform the action as them. This is
what mergerfs does and why mergerfs should always run as root.
In Linux setreuid syscalls apply only to the thread. glibc hides this
away by using realtime signals to inform all threads to change
credentials. Taking after Samba, mergerfs uses
`syscall(SYS_setreuid,...)` to set the callers credentials for that
thread only. Jumping back to `root` as necessary should escalated
privileges be needed (for instance: to clone paths between
filesystems).
For non-Linux systems, mergerfs uses a read-write lock and changes
credentials only when necessary. If multiple threads are to be user X
then only the first one will need to change the processes
credentials. So long as the other threads need to be user X they will
take a readlock allowing multiple threads to share the
credentials. Once a request comes in to run as user Y that thread will
attempt a write lock and change to Y's credentials when it can. If the
ability to give writers priority is supported then that flag will be
used so threads trying to change credentials don't starve. This isn't
the best solution but should work reasonably well assuming there are
few users.

88
mkdocs/docs/faq/technical_behavior_and_limitations.md

@ -146,7 +146,7 @@ You can remove the reserve by running: `tune2fs -m 0 <device>`
## I notice massive slowdowns of writes when enabling cache.files.
When file caching is enabled in any form (`cache.files!=off`) it will
When file caching is enabled in any form (`cache.files!=off`) it may
issue `getxattr` requests for `security.capability` prior to _every
single write_. This will usually result in performance degradation,
especially when using a network filesystem (such as NFS or SMB.)
@ -178,20 +178,21 @@ disadvantages to each one.
A FUSE based solution has all the downsides of FUSE:
- Higher IO latency due to the trips in and out of kernel space
- Higher general overhead due to trips in and out of kernel space
- Double caching when using page caching
- Misc limitations due to FUSE's design
* Higher IO latency due to the trips in and out of kernel space
(though now minimized with passthrough IO)
* Higher general overhead due to trips in and out of kernel space
* Double caching when using page caching
* Misc limitations due to FUSE's design
But FUSE also has a lot of upsides:
- Easier to offer a cross platform solution
- Easier forward and backward compatibility
- Easier updates for users
- Easier and faster release cadence
- Allows more flexibility in design and features
- Overall easier to write, secure, and maintain
- Much lower barrier to entry (getting code into the kernel takes a
* Easier to offer a cross platform solution
* Easier forward and backward compatibility
* Easier updates for users
* Easier and faster release cadence
* Allows more flexibility in design and features
* Overall easier to write, secure, and maintain
* Much lower barrier to entry (getting code into the kernel takes a
lot of time and effort initially)
@ -220,45 +221,56 @@ removed to simplify the codebase.
## How does mergerfs handle credentials?
mergerfs is a multithreaded application in order to handle requests
from the kernel concurrently. Each FUSE message has a header with
certain details about the request including the process ID (pid) of
the requesting application, the process' effective user id (uid), and
group id (gid). To ensure proper POSIX filesystem behavior and
security mergerfs must change its identity to match that of the
requester when performing the certain functions on the underlying
filesystem. As required by standards most Unix/POSIX based systems a
process and all its threads are under the same uid and gid. However,
on Linux each thread **may** have its own credentials. This allows
mergerfs to be multithreaded and for each thread to change to the
credentials as required by the incoming message it is
handling. However, currently on FreeBSD this is not possible (though
there has been
[discussions](https://wiki.freebsd.org/Per-Thread%20Credentials)) and
as such must change the credentials of the whole application when
actioning messages. mergerfs does optimize this behavior by only
changing credentials and locking the thread to do so if the process is
currently not the same as what is necessary by the incoming
request. As a result of this design FreeBSD may experience more
contention and therefore lower performance than Linux.
Additionally, mergerfs [utilizes a cache for supplemental
groups](../known_issues_bugs.md#supplemental-user-groups) due the the
high cost of querying that information.
group id (gid).
FUSE and the kernel have two ways of managing permissions. A kernel
side `default_permissions` option and leaving it to the FUSE
server. When default permissions is enabled the kernel will do the
entitlement checks and only allow requests through which should be
allowed according to normal POSIX permissions. When not enabled it is
the responsibility of the FUSE server, in this case mergerfs, to do
whatever is necessary to manage entitlements.
Prior to mergerfs v2.42.0 it would enable `default_permissions` but
also leveraged the uid and gid available in each FUSE request. The
thread actioning the request would change its credentials to match
those of the requesting application to ensure permissions were
properly handled as well as dealing with some quirks of non-POSIX
compatible filesystems. However, this strategy had two major
issues. First, it was not compatible with the new `allow-idmap`
feature of FUSE which allows a filesystem to advertise it can be used
with id mapping which is often used with containers. Secondly, it
caused permission issues when the kernel would say something was
allowed but due to the way mergerfs was changing creds would
fail. This mostly came in the form of `chroot`ed setups like used in
containers. Neither of these tended to impact casual users but did
break some niche or power user use cases. Since the casual user won't
notice the subtle changes and it would enable new use cases it was
decided that the credential handling would change.
As of v2.42.0 mergerfs now runs as root more generally. Either
changing credentials when creating files (Linux) or chown'ing them
after creation (FreeBSD).
As a result of this change it is now necessary for the FUSE
`default_permissions` feature be used for proper entitlements
management. mergerfs does allow it to be disabled but it should only
be done so for debugging purposes.
## Does mergerfs support idmap?
Yes. At least in so far as it's been enabled now the FUSE itself
allows a filesystem to indicate it is allowed.
Yes, by setting [allow-idmap=true](../config/options.md) (which is the
default.)
Requires that
[kernel-permissions-check](../config/kernel-permissions-check.md) be
enabled (the default.)
If there are any usage issues contact the [author](../support.md).
## What happens if a branch filesystem blocks?

21
mkdocs/docs/faq/usage_and_functionality.md

@ -1,5 +1,26 @@
# Usage and Functionality
## What should mergerfs NOT be used for?
* Situations where you need large amounts of contiguous space beyond
that available on any singular device. Such as putting 10GiB a file
on 2 6GiB filesystems.
* databases: Even if the database stored data in separate files
(mergerfs wouldn't offer much otherwise) the higher latency of the
indirection will harm performance. Though if it is a sqlite3
database then its likely fine.
* VM images: For the same reasons as databases. VM images are accessed
very aggressively and mergerfs will introduce a lot of extra latency.
* As replacement for RAID: mergerfs is just for pooling branches. If
you need device performance aggregation or high availability you
should stick with RAID. However, it is fine to put a filesystem
which is on a RAID setup in mergerfs.
**However, if using [passthrough](../config/passthrough.md) the
performance related issues above are less likely to be a concern. Best
to do testing for your specific use case.**
## What happens when file paths overlap?
It depends on the situation and the configuration of mergerfs. The

35
mkdocs/docs/known_issues_bugs.md

@ -4,10 +4,17 @@
### FreeBSD version
* FreeBSD doesn't have per thread credentials meaning threads must
block to change credentials as required by numerous filesystem
functions. This impacts performance.
* FreeBSD's FUSE implementation is lacking many features of Linux.
* [https://wiki.freebsd.org/FUSEFS](https://wiki.freebsd.org/FUSEFS)
* FreeBSD does not have per thread credentials nor Linux capabilities
like abilities meaning it runs as root and has to create files as
root then chown them rather than changing credentials and creating
the file as the uid:gid.
* FreeBSD does not have `getdents` like APIs for reading
directories. As a result it uses traditional `readdir`.
* FreeBSD's FUSE implementation is lacking many features of the Linux
version.
* runtime interface
* ioctl support
* IO passthrough
* statx
* lazy umount
@ -16,26 +23,10 @@
* kernel symlink caching
* kernel readdir caching
* writeback caching
* idmap
* ...
### Supplemental user groups
#### Supplemental group caching
Due to the high cost of querying supplemental groups it is necessary
for mergerfs to cache the list. As a result if supplemental groups are
changed while mergerfs is running the cache may become stale. This
generally isn't a problem as these groups tend not to be changed
often. However, the cache has a default expiry of 1 hour and after 12
hours of no usage the cache entry will be removed altogether.
See [gidcache.expire-timeout and
gid-cache.remove-timeout](config/options.md) and the [runtime
interface](runtime_interface.md#commands) for forcing expiry and clearing of
the cache.
#### Host vs Container identity
While not a bug some users have found when using containers that
@ -130,7 +121,7 @@ Details on enabling `mmap` can be found at:
That said it is recommended that config and runtime files be stored on
SSDs on a regular filesystem for performance reasons. See [What should
mergerfs NOT be used
for?](faq/recommendations_and_warnings.md#what-should-mergerfs-not-be-used-for). Though
for?](faq/usage_and_functionality.md#what-should-mergerfs-not-be-used-for). Though
with [passthrough.io](config/passthrough.md) enabled that is less
of a concern.

1
mkdocs/docs/resource_usage.md

@ -11,7 +11,6 @@
* 1MB+ pre reader thread + inflight processing for messages
depending on [fuse-msg-size](config/fuse-msg-size.md)
* buffers allocated temporarily for reading directories
* [gidcache](faq/technical_behavior_and_limitations.md#how-does-mergerfs-handle-credentials)
* FUSE nodes
* noforget forgotten nodes

2
mkdocs/docs/runtime_interface.md

@ -143,8 +143,6 @@ allows a value.
| user.mergerfs.cmd.gc | (empty) |Trigger a thorough garbage collection of certain pools of resources. The xattr value is not used. |
| user.mergerfs.cmd.gc1 | (empty) | Trigger a simple garbage collection of certain pools of resources. This is also done on a timer. |
| user.mergerfs.cmd.invalidate-all-nodes | (empty) | Attempts to invalidate FUSE file nodes. Primarily used for debugging. |
| user.mergerfs.cmd.invalidate-gid-cache | (empty) | Invalidates all entries in gid cache. Primarily used for debugging but can also be useful in cases where gid supplemental groups change. |
| user.mergerfs.cmd.clear-gid-cache | (empty) | Clears all entries in the gid cache. Primarily used for debugging but can also be useful in cases where gid supplemental groups change. |
```
[trapexit:/mnt/mergerfs] $ setfattr -n user.mergerfs.cmd.gc /mnt/mergerfs/.mergerfs

1
mkdocs/mkdocs.yml

@ -113,7 +113,6 @@ nav:
- faq/usage_and_functionality.md
- faq/reliability_and_scalability.md
- faq/compatibility_and_integration.md
- faq/recommendations_and_warnings.md
- faq/technical_behavior_and_limitations.md
- faq/have_you_considered.md
- faq/limit_drive_spinup.md

104
src/caps.cpp

@ -0,0 +1,104 @@
#include "caps.hpp"
#if defined __linux__
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <grp.h>
#include <linux/capability.h>
#include <linux/securebits.h>
static
int
capset(cap_user_header_t header_,
const cap_user_data_t data_)
{
return ::syscall(SYS_capset,header_,data_);
}
static
int
capget(cap_user_header_t header_,
cap_user_data_t data_)
{
return ::syscall(SYS_capget,header_,data_);
}
static
int
capset(int cap_bit_)
{
int rv;
struct __user_cap_header_struct header;
struct __user_cap_data_struct data[2];
header.version = _LINUX_CAPABILITY_VERSION_3;
header.pid = 0;
rv = capget(&header,data);
if(rv < 0)
return rv;
int word = cap_bit_ / 32;
int bit = cap_bit_ % 32;
data[word].permitted |= (1 << bit);
data[word].effective |= (1 << bit);
data[word].inheritable |= (1 << bit);
rv = capset(&header,data);
if(rv < 0)
return rv;
return 0;
}
int
caps::setup()
{
int rv;
rv = capset(CAP_DAC_OVERRIDE);
if(rv < 0)
return rv;
rv = capset(CAP_DAC_READ_SEARCH);
if(rv < 0)
return rv;
rv = capset(CAP_FOWNER);
if(rv < 0)
return rv;
rv = capset(CAP_CHOWN);
if(rv < 0)
return rv;
rv = capset(CAP_SETUID);
if(rv < 0)
return rv;
rv = capset(CAP_SETGID);
if(rv < 0)
return rv;
rv = prctl(PR_SET_SECUREBITS,
SECBIT_KEEP_CAPS | SECBIT_NO_SETUID_FIXUP);
if(rv < 0)
return -errno;
return 0;
}
#else
#include <errno.h>
int
caps::setup()
{
return -ENOSYS;
}
#endif

6
src/caps.hpp

@ -0,0 +1,6 @@
#pragma once
namespace caps
{
int setup();
}

8
src/config.cpp

@ -78,7 +78,7 @@ Config::CfgConfigFile::to_string() const
Config::Config()
:
allow_idmap(false),
allow_idmap(true),
async_read(true),
branches(),
branches_mount_timeout(0),
@ -102,8 +102,6 @@ Config::Config()
fsname(),
func(),
fuse_msg_size("1M"),
gid_cache_expire_timeout(60 * 60),
gid_cache_remove_timeout(60 * 60 * 12),
handle_killpriv(true),
handle_killpriv_v2(true),
ignorepponrename(false),
@ -250,8 +248,8 @@ Config::Config()
_map["func.utimens"] = &func.utimens;
_map["fuse-msg-size"] = &fuse_msg_size;
_map["gid"] = &_gid;
_map["gid-cache.expire-timeout"] = &gid_cache_expire_timeout;
_map["gid-cache.remove-timeout"] = &gid_cache_remove_timeout;
_map["gid-cache.expire-timeout"] = &_dummy;
_map["gid-cache.remove-timeout"] = &_dummy;
_map["handle-killpriv"] = &handle_killpriv;
_map["handle-killpriv-v2"] = &handle_killpriv_v2;
_map["hard-remove"] = &_dummy;

3
src/config.hpp

@ -22,7 +22,6 @@
#include "config_dummy.hpp"
#include "config_flushonclose.hpp"
#include "config_follow_symlinks.hpp"
#include "config_gidcache.hpp"
#include "config_inodecalc.hpp"
#include "config_link_exdev.hpp"
#include "config_log_metrics.hpp"
@ -133,8 +132,6 @@ public:
ConfigSTR fsname;
Funcs func;
ConfigPageSize fuse_msg_size;
GIDCacheExpireTimeout gid_cache_expire_timeout;
GIDCacheRemoveTimeout gid_cache_remove_timeout;
ConfigBOOL handle_killpriv;
ConfigBOOL handle_killpriv_v2;
ConfigBOOL ignorepponrename;

65
src/config_gidcache.cpp

@ -1,65 +0,0 @@
#include "config_gidcache.hpp"
#include "gidcache.hpp"
#include "to_string.hpp"
#include "from_string.hpp"
GIDCacheExpireTimeout::GIDCacheExpireTimeout(const int i_)
{
GIDCache::expire_timeout = i_;
}
GIDCacheExpireTimeout::GIDCacheExpireTimeout(const std::string &s_)
{
from_string(s_);
}
std::string
GIDCacheExpireTimeout::to_string(void) const
{
return str::to(GIDCache::expire_timeout);
}
int
GIDCacheExpireTimeout::from_string(const std::string_view s_)
{
int rv;
rv = str::from(s_,&GIDCache::expire_timeout);
if(rv < 0)
return rv;
return 0;
}
GIDCacheRemoveTimeout::GIDCacheRemoveTimeout(const int i_)
{
GIDCache::remove_timeout = i_;
}
GIDCacheRemoveTimeout::GIDCacheRemoveTimeout(const std::string &s_)
{
from_string(s_);
}
std::string
GIDCacheRemoveTimeout::to_string(void) const
{
return str::to(GIDCache::remove_timeout);
}
int
GIDCacheRemoveTimeout::from_string(const std::string_view s_)
{
int rv;
rv = str::from(s_,&GIDCache::remove_timeout);
if(rv < 0)
return rv;
return 0;
}

25
src/config_gidcache.hpp

@ -1,25 +0,0 @@
#pragma once
#include "tofrom_string.hpp"
class GIDCacheExpireTimeout : public ToFromString
{
public:
GIDCacheExpireTimeout(const int i = 60 * 60);
GIDCacheExpireTimeout(const std::string &);
public:
std::string to_string(void) const final;
int from_string(const std::string_view) final;
};
class GIDCacheRemoveTimeout : public ToFromString
{
public:
GIDCacheRemoveTimeout(const int = 60 * 60 * 12);
GIDCacheRemoveTimeout(const std::string &);
public:
std::string to_string(void) const final;
int from_string(const std::string_view) final;
};

4
src/fs_attr_linux.icpp

@ -31,7 +31,7 @@ using std::string;
static
int
_get_fs_ioc_flags(const int fd,
int &flags)
int &flags)
{
int rv;
@ -45,7 +45,7 @@ _get_fs_ioc_flags(const int fd,
static
int
_get_fs_ioc_flags(const string &file,
int &flags)
int &flags)
{
int fd;
int rv;

75
src/fs_clonepath.cpp

@ -27,6 +27,16 @@
#include "fs_xattr.hpp"
#include "ugid.hpp"
#include "fs_close.hpp"
#include "fs_fstat.hpp"
#include "fs_mkdirat.hpp"
#include "fs_openat.hpp"
#include "fs_fchown.hpp"
#include "fs_fchmod.hpp"
#include "fs_futimens.hpp"
#include "scope_guard.hpp"
static
bool
@ -107,21 +117,62 @@ fs::clonepath(const fs::path &srcpath_,
return 0;
}
// WORK IN PROGRESS
static
int
fs::clonepath_as_root(const fs::path &srcpath_,
const fs::path &dstpath_,
const fs::path &relpath_,
const bool return_metadata_errors_)
_clonepath2(const int srcfd_,
const int dstfd_,
const fs::path &dirname_,
const bool return_metadata_errors_)
{
if(relpath_.empty())
return 0;
if(srcpath_ == dstpath_)
int rv;
int srcdirfd;
int dstdirfd;
struct stat st;
if(dirname_.empty())
return 0;
const ugid::SetRootGuard ugid_guard;
rv = fs::mkdirat(dstfd_,dirname_,0);
if(rv < 0)
return ((rv == -EEXIST) ? 0 : rv);
srcdirfd = fs::openat(srcfd_,dirname_,O_DIRECTORY);
if(srcdirfd < 0)
return srcdirfd;
DEFER { fs::close(srcdirfd); };
dstdirfd = fs::openat(dstfd_,dirname_,O_DIRECTORY);
if(dstdirfd < 0)
return dstdirfd;
DEFER { fs::close(dstdirfd); };
rv = fs::attr::copy(srcdirfd,dstdirfd,FS_ATTR_CLEAR_IMMUTABLE);
if(return_metadata_errors_ && (rv < 0) && !::_ignorable_error(-rv))
return rv;
rv = fs::xattr::copy(srcdirfd,dstdirfd);
if(return_metadata_errors_ && (rv < 0) && !::_ignorable_error(-rv))
return rv;
rv = fs::fstat(srcdirfd,&st);
if(rv < 0)
return rv;
if(!S_ISDIR(st.st_mode))
return -ENOTDIR;
rv = fs::fchown_check_on_error(dstdirfd,st);
if(rv < 0)
return rv;
rv = fs::fchmod_check_on_error(dstdirfd,st);
if(rv < 0)
return rv;
return fs::clonepath(srcpath_,
dstpath_,
relpath_,
return_metadata_errors_);
rv = fs::futimens(dstdirfd,st);
if(rv < 0)
return rv;
return 0;
}

5
src/fs_clonepath.hpp

@ -25,9 +25,4 @@ namespace fs
const fs::path &dstpath,
const fs::path &relpath,
const bool return_metadata_errors = false);
int clonepath_as_root(const fs::path &srcpath,
const fs::path &dstpath,
const fs::path &relpath,
const bool return_metadata_errors = false);
}

2
src/fs_is_rofs.hpp

@ -49,8 +49,6 @@ namespace fs
bool
is_rofs(const fs::path &path_)
{
ugid::SetRootGuard const ugid;
int fd;
std::string tmp_filepath;

49
src/fs_mkdir_as.hpp

@ -0,0 +1,49 @@
#pragma once
#include "fs_mkdir.hpp"
#include "ugid.hpp"
#if defined __linux__
namespace fs
{
template<typename T>
static
inline
int
mkdir_as(const ugid_t ugid_,
const T &path_,
const mode_t mode_)
{
const ugid::SetGuard _(ugid_);
return fs::mkdir(path_,mode_);
}
}
#elif defined __FreeBSD__
#include "fs_lchown.hpp"
namespace fs
{
template<typename T>
static
inline
int
mkdir_as(const ugid_t ugid_,
const T &path_,
const mode_t mode_)
{
int rv;
rv = fs::mkdir(path_,mode_);
if(rv < 0)
return rv;
fs::lchown(path_,ugid_.uid,ugid_.gid);
return 0;
}
}
#else
#error "Not Supported"
#endif

35
src/fs_mkdir_as_root.hpp

@ -1,35 +0,0 @@
/*
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_);
}
}

48
src/fs_mkdirat.hpp

@ -0,0 +1,48 @@
#pragma once
#include "to_neg_errno.hpp"
#include "fs_path.hpp"
#include <fcntl.h>
#include <sys/stat.h>
namespace fs
{
static
inline
int
mkdirat(const int dirfd_,
const char *pathname_,
const mode_t mode_)
{
int rv;
rv = ::mkdirat(dirfd_,pathname_,mode_);
return ::to_neg_errno(rv);
}
static
inline
int
mkdirat(const int dirfd_,
const std::string &pathname_,
const mode_t mode_)
{
return fs::mkdirat(dirfd_,
pathname_.c_str(),
mode_);
}
static
inline
int
mkdirat(const int dirfd_,
const fs::path &pathname_,
const mode_t mode_)
{
return fs::mkdirat(dirfd_,
pathname_.c_str(),
mode_);
}
}

49
src/fs_mknod_as.hpp

@ -0,0 +1,49 @@
#pragma once
#include "fs_mknod.hpp"
#include "ugid.hpp"
#if defined __linux__
namespace fs
{
template<typename T>
static
inline
int
mknod_as(const ugid_t ugid_,
const T &path_,
const mode_t mode_,
const dev_t dev_)
{
const ugid::SetGuard _(ugid_);
return fs::mknod(path_,mode_,dev_);
}
}
#elif defined __FreeBSD__
#include "fs_lchown.hpp"
namespace fs
{
template<typename T>
static
inline
int
mknod_as(const ugid_t ugid_,
const T &path_,
const mode_t mode_,
const dev_t dev_)
{
int rv;
rv = fs::mknod(path_,mode_,dev_);
fs::lchown(path_,ugid_.uid,ugid_.gid);
return rv;
}
}
#else
#error "Not Supported!"
#endif

2
src/fs_movefile_and_open.cpp

@ -137,8 +137,6 @@ fs::movefile_and_open_as_root(const Policy::Create &policy_,
const fs::path &fusepath_,
const int origfd_)
{
const ugid::Set ugid(0,0);
return fs::movefile_and_open(policy_,
branches_,
branchpath_,

56
src/fs_open_as.hpp

@ -0,0 +1,56 @@
#pragma once
#include "fs_open.hpp"
#include "ugid.hpp"
// Linux can set ugid because it allows for credentials per
// thread. FreeBSD however does not and must create as root then
// chown. Another option for both platforms would be to create a temp
// file, chown, then rename to target. Unfortunately, depending on if
// the target filesystem is POSIX compliant or uses POSIX / extended
// ACLs it is not possible to know what is best.
#if defined __linux__
namespace fs
{
static
inline
int
open_as(const ugid_t ugid_,
const fs::path &path_,
const int flags_,
const mode_t mode_)
{
const ugid::SetGuard _(ugid_);
return fs::open(path_,flags_,mode_);
}
}
#elif defined __FreeBSD__
#include "fs_fchown.hpp"
namespace fs
{
static
inline
int
open_as(const ugid_t ugid_,
const fs::path &path_,
const int flags_,
const mode_t mode_)
{
int rv;
rv = fs::open(path_,flags_,mode_);
if(rv < 0)
return rv;
fs::fchown(rv,ugid_.uid,ugid_.gid);
return rv;
}
}
#else
#error "Not Supported!"
#endif

49
src/fs_symlink_as.hpp

@ -0,0 +1,49 @@
#pragma once
#include "fs_symlink.hpp"
#include "ugid.hpp"
#if defined __linux__
namespace fs
{
template<typename T>
static
inline
int
symlink_as(const ugid_t ugid_,
const char *target_,
const T &linkpath_)
{
const ugid::SetGuard _(ugid_);
return fs::symlink(target_,linkpath_);
}
}
#elif defined __FreeBSD__
#include "fs_lchown.hpp"
namespace fs
{
template<typename T>
static
inline
int
symlink_as(const ugid_t ugid_,
const char *target_,
const T &linkpath_)
{
int rv;
rv = fs::symlink(target_,linkpath_);
if(rv < 0)
return rv;
fs::lchown(linkpath_,ugid_.uid,ugid_.gid);
return 0;
}
}
#else
#error "Not Supported!"
#endif

3
src/fuse_access.cpp

@ -54,8 +54,7 @@ FUSE::access(const fuse_req_ctx_t *ctx_,
const char *fusepath_,
int mask_)
{
const fs::path fusepath{fusepath_};
const ugid::Set ugid(ctx_->uid,ctx_->gid);
const fs::path fusepath{fusepath_};
return ::_access(cfg.func.access.policy,
cfg.branches,

1
src/fuse_chmod.cpp

@ -108,7 +108,6 @@ FUSE::chmod(const fuse_req_ctx_t *ctx_,
mode_t mode_)
{
const fs::path fusepath{fusepath_};
const ugid::Set ugid(ctx_->uid,ctx_->gid);
return ::_chmod(fusepath,mode_);
}

3
src/fuse_chown.cpp

@ -99,8 +99,7 @@ FUSE::chown(const fuse_req_ctx_t *ctx_,
uid_t uid_,
gid_t gid_)
{
const fs::path fusepath{fusepath_};
const ugid::Set ugid(ctx_->uid,ctx_->gid);
const fs::path fusepath{fusepath_};
return ::_chown(cfg.func.chown.policy,
cfg.func.getattr.policy,

33
src/fuse_create.cpp

@ -19,11 +19,15 @@
#include "state.hpp"
#include "config.hpp"
#include "fs_readlink.hpp"
#include "errno.hpp"
#include "fileinfo.hpp"
#include "fs_acl.hpp"
#include "fs_close.hpp"
#include "fs_clonepath.hpp"
#include "fs_open.hpp"
#include "fs_open_as.hpp"
#include "fs_openat.hpp"
#include "fs_path.hpp"
#include "fuse_passthrough.hpp"
#include "procfs.hpp"
@ -132,7 +136,8 @@ _config_to_ffi_flags(Config &cfg_,
static
int
_create_core(const fs::path &fullpath_,
_create_core(const ugid_t ugid_,
const fs::path &fullpath_,
mode_t mode_,
const mode_t umask_,
const int flags_)
@ -140,12 +145,13 @@ _create_core(const fs::path &fullpath_,
if(!fs::acl::dir_has_defaults(fullpath_))
mode_ &= ~umask_;
return fs::open(fullpath_,flags_,mode_);
return fs::open_as(ugid_,fullpath_,flags_,mode_);
}
static
int
_create_core(const Branch *branch_,
_create_core(const ugid_t ugid_,
const Branch *branch_,
const fs::path &fusepath_,
fuse_file_info_t *ffi_,
const mode_t mode_,
@ -157,7 +163,7 @@ _create_core(const Branch *branch_,
fullpath = branch_->path / fusepath_;
rv = ::_create_core(fullpath,mode_,umask_,ffi_->flags);
rv = ::_create_core(ugid_,fullpath,mode_,umask_,ffi_->flags);
if(rv < 0)
return rv;
@ -170,7 +176,8 @@ _create_core(const Branch *branch_,
static
int
_create(const Policy::Search &searchFunc_,
_create(const ugid_t ugid_,
const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const fs::path &fusepath_,
@ -194,13 +201,14 @@ _create(const Policy::Search &searchFunc_,
if(rv < 0)
return rv;
rv = fs::clonepath_as_root(existingpaths[0]->path,
createpaths[0]->path,
fusedirpath);
rv = fs::clonepath(existingpaths[0]->path,
createpaths[0]->path,
fusedirpath);
if(rv < 0)
return rv;
return ::_create_core(createpaths[0],
return ::_create_core(ugid_,
createpaths[0],
fusepath_,
ffi_,
mode_,
@ -225,7 +233,6 @@ _create_for_insert_lambda(const fuse_req_ctx_t *ctx_,
{
int rv;
FileInfo *fi;
const ugid::Set ugid(ctx_->uid,ctx_->gid);
::_config_to_ffi_flags(cfg,ctx_->pid,ffi_);
if(cfg.cache_writeback)
@ -233,7 +240,8 @@ _create_for_insert_lambda(const fuse_req_ctx_t *ctx_,
ffi_->noflush = !::_calculate_flush(cfg.flushonclose,
ffi_->flags);
rv = ::_create(cfg.func.getattr.policy,
rv = ::_create(ctx_,
cfg.func.getattr.policy,
cfg.func.create.policy,
cfg.branches,
fusepath_,
@ -243,7 +251,8 @@ _create_for_insert_lambda(const fuse_req_ctx_t *ctx_,
if(rv == -EROFS)
{
cfg.branches.find_and_set_mode_ro();
rv = ::_create(cfg.func.getattr.policy,
rv = ::_create(ctx_,
cfg.func.getattr.policy,
cfg.func.create.policy,
cfg.branches,
fusepath_,

2
src/fuse_getattr.cpp

@ -213,8 +213,6 @@ FUSE::getattr(const fuse_req_ctx_t *ctx_,
struct stat *st_,
fuse_timeouts_t *timeout_)
{
const ugid::Set ugid(ctx_);
return FUSE::getattr(fusepath_,st_,timeout_);
}

2
src/fuse_getxattr.cpp

@ -193,8 +193,6 @@ FUSE::getxattr(const fuse_req_ctx_t *ctx_,
if(cfg.xattr.to_int())
return -cfg.xattr.to_int();
const ugid::Set ugid(ctx_->uid,ctx_->gid);
return ::_getxattr(cfg.func.getxattr.policy,
cfg.branches,
fusepath,

1
src/fuse_init.cpp

@ -187,7 +187,6 @@ void *
FUSE::init(fuse_conn_info *conn_)
{
procfs::init();
ugid::init();
cfg.readdir.initialize();
::_want_if_capable(conn_,FUSE_CAP_ASYNC_DIO);

7
src/fuse_ioctl.cpp

@ -29,7 +29,6 @@
#include "fs_ioctl.hpp"
#include "fs_open.hpp"
#include "fs_path.hpp"
#include "gidcache.hpp"
#include "mergerfs_ioctl.hpp"
#include "str.hpp"
#include "ugid.hpp"
@ -113,8 +112,7 @@ _ioctl_file(const fuse_req_ctx_t *ctx_,
void *data_,
uint32_t *out_bufsz_)
{
FileInfo *fi = FileInfo::from_fh(ffi_->fh);
const ugid::Set ugid(ctx_);
FileInfo *fi = FileInfo::from_fh(ffi_->fh);
return ::_ioctl(fi->fd,cmd_,data_,out_bufsz_);
}
@ -162,8 +160,7 @@ _ioctl_dir(const fuse_req_ctx_t *ctx_,
void *data_,
uint32_t *out_bufsz_)
{
DirInfo *di = DirInfo::from_fh(ffi_->fh);
const ugid::Set ugid(ctx_);
DirInfo *di = DirInfo::from_fh(ffi_->fh);
return ::_ioctl_dir_base(cfg.func.open.policy,
cfg.branches,

9
src/fuse_link.cpp

@ -55,7 +55,9 @@ _link_create_path_loop(const std::vector<Branch*> &oldbranches_,
rv = fs::link(oldfullpath,newfullpath);
if(rv == -ENOENT)
{
rv = fs::clonepath_as_root(newbranch_->path,oldbranch->path,newfusedirpath_);
rv = fs::clonepath(newbranch_->path,
oldbranch->path,
newfusedirpath_);
if(rv == 0)
rv = fs::link(oldfullpath,newfullpath);
}
@ -328,9 +330,8 @@ FUSE::link(const fuse_req_ctx_t *ctx_,
fuse_timeouts_t *timeouts_)
{
int rv;
const fs::path oldpath{oldpath_};
const fs::path newpath{newpath_};
const ugid::Set ugid(ctx_);
const fs::path oldpath{oldpath_};
const fs::path newpath{newpath_};
rv = ::_link(ctx_,oldpath,newpath,st_,timeouts_);
if(rv == -EXDEV)

2
src/fuse_listxattr.cpp

@ -143,8 +143,6 @@ FUSE::listxattr(const fuse_req_ctx_t *ctx_,
return -ENOSYS;
}
const ugid::Set ugid(ctx_);
return ::_listxattr(cfg.func.listxattr.policy,
cfg.branches,
fusepath,

39
src/fuse_mkdir.cpp

@ -21,7 +21,7 @@
#include "error.hpp"
#include "fs_acl.hpp"
#include "fs_clonepath.hpp"
#include "fs_mkdir.hpp"
#include "fs_mkdir_as.hpp"
#include "fs_path.hpp"
#include "policy.hpp"
#include "ugid.hpp"
@ -33,19 +33,21 @@
static
int
_mkdir_core(const fs::path &fullpath_,
_mkdir_core(const ugid_t ugid_,
const fs::path &fullpath_,
mode_t mode_,
const mode_t umask_)
{
if(!fs::acl::dir_has_defaults(fullpath_))
mode_ &= ~umask_;
return fs::mkdir(fullpath_,mode_);
return fs::mkdir_as(ugid_,fullpath_,mode_);
}
static
int
_mkdir_loop_core(const fs::path &createpath_,
_mkdir_loop_core(const ugid_t ugid_,
const fs::path &createpath_,
const fs::path &fusepath_,
const mode_t mode_,
const mode_t umask_)
@ -55,14 +57,15 @@ _mkdir_loop_core(const fs::path &createpath_,
fullpath = createpath_ / fusepath_;
rv = ::_mkdir_core(fullpath,mode_,umask_);
rv = ::_mkdir_core(ugid_,fullpath,mode_,umask_);
return rv;
}
static
int
_mkdir_loop(const Branch *existingbranch_,
_mkdir_loop(const ugid_t ugid_,
const Branch *existingbranch_,
const std::vector<Branch*> &createbranches_,
const fs::path &fusepath_,
const fs::path &fusedirpath_,
@ -74,16 +77,17 @@ _mkdir_loop(const Branch *existingbranch_,
for(const auto &createbranch : createbranches_)
{
rv = fs::clonepath_as_root(existingbranch_->path,
createbranch->path,
fusedirpath_);
rv = fs::clonepath(existingbranch_->path,
createbranch->path,
fusedirpath_);
if(rv < 0)
{
err = rv;
continue;
}
err = ::_mkdir_loop_core(createbranch->path,
err = ::_mkdir_loop_core(ugid_,
createbranch->path,
fusepath_,
mode_,
umask_);
@ -94,7 +98,8 @@ _mkdir_loop(const Branch *existingbranch_,
static
int
_mkdir(const Policy::Search &getattrPolicy_,
_mkdir(const ugid_t ugid_,
const Policy::Search &getattrPolicy_,
const Policy::Create &mkdirPolicy_,
const Branches &branches_,
const fs::path &fusepath_,
@ -116,7 +121,8 @@ _mkdir(const Policy::Search &getattrPolicy_,
if(rv < 0)
return rv;
return ::_mkdir_loop(existingbranches[0],
return ::_mkdir_loop(ugid_,
existingbranches[0],
createbranches,
fusepath_,
fusedirpath,
@ -130,10 +136,10 @@ FUSE::mkdir(const fuse_req_ctx_t *ctx_,
mode_t mode_)
{
int rv;
const fs::path fusepath{fusepath_};
const ugid::Set ugid(ctx_);
const fs::path fusepath{fusepath_};
rv = ::_mkdir(cfg.func.getattr.policy,
rv = ::_mkdir(ctx_,
cfg.func.getattr.policy,
cfg.func.mkdir.policy,
cfg.branches,
fusepath,
@ -142,7 +148,8 @@ FUSE::mkdir(const fuse_req_ctx_t *ctx_,
if(rv == -EROFS)
{
cfg.branches.find_and_set_mode_ro();
rv = ::_mkdir(cfg.func.getattr.policy,
rv = ::_mkdir(ctx_,
cfg.func.getattr.policy,
cfg.func.mkdir.policy,
cfg.branches,
fusepath,

39
src/fuse_mknod.cpp

@ -20,7 +20,7 @@
#include "errno.hpp"
#include "error.hpp"
#include "fs_acl.hpp"
#include "fs_mknod.hpp"
#include "fs_mknod_as.hpp"
#include "fs_clonepath.hpp"
#include "fs_path.hpp"
#include "ugid.hpp"
@ -34,7 +34,8 @@
static
inline
int
_mknod_core(const fs::path &fullpath_,
_mknod_core(const ugid_t ugid_,
const fs::path &fullpath_,
mode_t mode_,
const mode_t umask_,
const dev_t dev_)
@ -42,12 +43,13 @@ _mknod_core(const fs::path &fullpath_,
if(!fs::acl::dir_has_defaults(fullpath_))
mode_ &= ~umask_;
return fs::mknod(fullpath_,mode_,dev_);
return fs::mknod_as(ugid_,fullpath_,mode_,dev_);
}
static
int
_mknod_loop_core(const fs::path &createbranch_,
_mknod_loop_core(const ugid_t ugid_,
const fs::path &createbranch_,
const fs::path &fusepath_,
const mode_t mode_,
const mode_t umask_,
@ -58,14 +60,15 @@ _mknod_loop_core(const fs::path &createbranch_,
fullpath = createbranch_ / fusepath_;
rv = ::_mknod_core(fullpath,mode_,umask_,dev_);
rv = ::_mknod_core(ugid_,fullpath,mode_,umask_,dev_);
return rv;
}
static
int
_mknod_loop(const fs::path &existingbranch_,
_mknod_loop(const ugid_t ugid_,
const fs::path &existingbranch_,
const std::vector<Branch*> &createbranches_,
const fs::path &fusepath_,
const fs::path &fusedirpath_,
@ -78,16 +81,17 @@ _mknod_loop(const fs::path &existingbranch_,
for(const auto &createbranch : createbranches_)
{
rv = fs::clonepath_as_root(existingbranch_,
createbranch->path,
fusedirpath_);
rv = fs::clonepath(existingbranch_,
createbranch->path,
fusedirpath_);
if(rv < 0)
{
err = rv;
continue;
}
err = ::_mknod_loop_core(createbranch->path,
err = ::_mknod_loop_core(ugid_,
createbranch->path,
fusepath_,
mode_,
umask_,
@ -99,7 +103,8 @@ _mknod_loop(const fs::path &existingbranch_,
static
int
_mknod(const Policy::Search &searchFunc_,
_mknod(const ugid_t ugid_,
const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const fs::path &fusepath_,
@ -122,7 +127,8 @@ _mknod(const Policy::Search &searchFunc_,
if(rv < 0)
return rv;
return ::_mknod_loop(existingbranches[0]->path,
return ::_mknod_loop(ugid_,
existingbranches[0]->path,
createbranches,
fusepath_,
fusedirpath,
@ -138,10 +144,10 @@ FUSE::mknod(const fuse_req_ctx_t *ctx_,
dev_t rdev_)
{
int rv;
const fs::path fusepath{fusepath_};
const ugid::Set ugid(ctx_);
const fs::path fusepath{fusepath_};
rv = ::_mknod(cfg.func.getattr.policy,
rv = ::_mknod(ctx_,
cfg.func.getattr.policy,
cfg.func.mknod.policy,
cfg.branches,
fusepath,
@ -151,7 +157,8 @@ FUSE::mknod(const fuse_req_ctx_t *ctx_,
if(rv == -EROFS)
{
cfg.branches.find_and_set_mode_ro();
rv = ::_mknod(cfg.func.getattr.policy,
rv = ::_mknod(ctx_,
cfg.func.getattr.policy,
cfg.func.mknod.policy,
cfg.branches,
fusepath,

2
src/fuse_open.cpp

@ -281,7 +281,6 @@ _open_for_insert_lambda(const fuse_req_ctx_t *ctx_,
{
int rv;
FileInfo *fi;
const ugid::Set ugid(ctx_);
::_config_to_ffi_flags(cfg,ctx_->pid,ffi_);
@ -337,7 +336,6 @@ _open_for_update_lambda(const fuse_req_ctx_t *ctx_,
State::OpenFile *of_)
{
int rv;
const ugid::Set ugid(ctx_);
::_config_to_ffi_flags(cfg,ctx_->pid,ffi_);

4
src/fuse_passthrough.hpp

@ -14,8 +14,6 @@ namespace FUSE
int
passthrough_open(const int fd_)
{
const ugid::SetRootGuard _;
return fuse_passthrough_open(fd_);
}
@ -24,8 +22,6 @@ namespace FUSE
int
passthrough_close(const int backing_id_)
{
const ugid::SetRootGuard _;
return fuse_passthrough_close(backing_id_);
}
}

12
src/fuse_readdir_cor.cpp

@ -48,9 +48,7 @@ int
_concurrent_readdir(ThreadPool &tp_,
const Branches::Ptr &branches_,
const fs::path &rel_dirpath_,
fuse_dirents_t *dirents_,
const uid_t uid_,
const gid_t gid_)
fuse_dirents_t *dirents_)
{
HashSet names;
std::mutex mutex;
@ -62,10 +60,8 @@ _concurrent_readdir(ThreadPool &tp_,
for(const auto &branch : *branches_)
{
auto func =
[&,dirents_,uid_,gid_]()
[&,dirents_]()
{
const ugid::Set ugid(uid_,gid_);
return ::_readdir(branch.path,
rel_dirpath_,
names,
@ -95,7 +91,5 @@ FUSE::ReadDirCOR::operator()(const fuse_req_ctx_t *ctx_,
return ::_concurrent_readdir(_tp,
cfg.branches,
di->fusepath,
dirents_,
ctx_->uid,
ctx_->gid);
dirents_);
}

10
src/fuse_readdir_cosr.cpp

@ -45,16 +45,14 @@ int
_readdir(ThreadPool &tp_,
const Branches::Ptr &branches_,
const fs::path &rel_dirpath_,
fuse_dirents_t *dirents_,
uid_t const uid_,
gid_t const gid_)
fuse_dirents_t *dirents_)
{
int rv;
std::vector<std::future<DirRV>> futures;
fuse_dirents_reset(dirents_);
futures = ::_opendir(tp_,branches_,rel_dirpath_,uid_,gid_);
futures = ::_opendir(tp_,branches_,rel_dirpath_);
rv = ::_readdir(futures,rel_dirpath_,dirents_);
return rv;
@ -70,7 +68,5 @@ FUSE::ReadDirCOSR::operator()(const fuse_req_ctx_t *ctx_,
return ::_readdir(_tp,
cfg.branches,
di->fusepath,
dirents_,
ctx_->uid,
ctx_->gid);
dirents_);
}

7
src/fuse_readdir_cosr_getdents.icpp

@ -28,9 +28,7 @@ inline
std::vector<std::future<DirRV>>
_opendir(ThreadPool &tp_,
const Branches::Ptr &branches_,
const fs::path &rel_dirpath_,
uid_t const uid_,
gid_t const gid_)
const fs::path &rel_dirpath_)
{
std::vector<std::future<DirRV>> futures;
@ -39,11 +37,10 @@ _opendir(ThreadPool &tp_,
for(const auto &branch : *branches_)
{
auto func =
[&branch,&rel_dirpath_,uid_,gid_]()
[&branch,&rel_dirpath_]()
{
int fd;
fs::path abs_dirpath;
const ugid::Set ugid(uid_,gid_);
abs_dirpath = branch.path / rel_dirpath_;

7
src/fuse_readdir_cosr_readdir.icpp

@ -37,9 +37,7 @@ inline
std::vector<std::future<DirRV>>
_opendir(ThreadPool &tp_,
const Branches::Ptr &branches_,
const fs::path &rel_dirpath_,
uid_t const uid_,
gid_t const gid_)
const fs::path &rel_dirpath_)
{
std::vector<std::future<DirRV>> futures;
@ -48,11 +46,10 @@ _opendir(ThreadPool &tp_,
for(const auto &branch : *branches_)
{
auto func =
[&branch,&rel_dirpath_,uid_,gid_]()
[&branch,&rel_dirpath_]()
{
DIR *dir;
fs::path abs_dirpath;
const ugid::Set ugid(uid_,gid_);
abs_dirpath = branch.path / rel_dirpath_;

3
src/fuse_readdir_seq.cpp

@ -33,8 +33,7 @@ FUSE::ReadDirSeq::operator()(const fuse_req_ctx_t *ctx_,
const fuse_file_info_t *ffi_,
fuse_dirents_t *dirents_)
{
DirInfo *di = DirInfo::from_fh(ffi_->fh);
const ugid::Set ugid(ctx_);
DirInfo *di = DirInfo::from_fh(ffi_->fh);
return ::_readdir(cfg.branches,
di->fusepath,

3
src/fuse_readlink.cpp

@ -120,8 +120,7 @@ FUSE::readlink(const fuse_req_ctx_t *ctx_,
char *buf_,
size_t size_)
{
const fs::path fusepath{fusepath_};
const ugid::Set ugid(ctx_);
const fs::path fusepath{fusepath_};
return ::_readlink(cfg.func.readlink.policy,
cfg.branches,

2
src/fuse_removexattr.cpp

@ -101,8 +101,6 @@ FUSE::removexattr(const fuse_req_ctx_t *ctx_,
if(cfg.xattr.to_int())
return -cfg.xattr.to_int();
const ugid::Set ugid(ctx_);
return ::_removexattr(cfg.func.removexattr.policy,
cfg.func.getxattr.policy,
cfg.branches,

12
src/fuse_rename.cpp

@ -21,7 +21,7 @@
#include "errno.hpp"
#include "fs_clonepath.hpp"
#include "fs_link.hpp"
#include "fs_mkdir_as_root.hpp"
#include "fs_mkdir_as.hpp"
#include "fs_path.hpp"
#include "fs_remove.hpp"
#include "fs_rename.hpp"
@ -105,9 +105,9 @@ _rename_create_path(const Policy::Search &searchPolicy_,
rv = fs::rename(oldfullpath,newfullpath);
if(rv < 0)
{
rv = fs::clonepath_as_root(newbranches[0]->path,
branch.path,
newfusepath_.parent_path());
rv = fs::clonepath(newbranches[0]->path,
branch.path,
newfusepath_.parent_path());
if(rv >= 0)
rv = fs::rename(oldfullpath,newfullpath);
}
@ -209,7 +209,6 @@ _rename_exdev_rename_target(const Policy::Action &actionPolicy_,
if(rv < 0)
return rv;
ugid::SetRootGuard ugidGuard;
for(auto &branch : obranches_)
{
clonesrc = branch->path;
@ -219,7 +218,7 @@ _rename_exdev_rename_target(const Policy::Action &actionPolicy_,
rv = fs::clonepath(clonesrc,clonedst,oldfusepath_.parent_path());
if(rv == -ENOENT)
{
fs::mkdir(clonedst,01777);
fs::mkdir_as({0,0},clonedst,01777);
rv = fs::clonepath(clonesrc,clonedst,oldfusepath_.parent_path());
}
@ -355,7 +354,6 @@ FUSE::rename(const fuse_req_ctx_t *ctx_,
int rv;
const fs::path oldfusepath{oldfusepath_};
const fs::path newfusepath{newfusepath_};
const ugid::Set ugid(ctx_);
rv = ::_rename(oldfusepath,newfusepath);
if(rv == -EXDEV)

1
src/fuse_rmdir.cpp

@ -96,7 +96,6 @@ FUSE::rmdir(const fuse_req_ctx_t *ctx_,
const char *fusepath_)
{
const fs::path fusepath{fusepath_};
const ugid::Set ugid(ctx_);
return ::_rmdir(cfg.func.rmdir.policy,
cfg.branches,

7
src/fuse_setxattr.cpp

@ -22,7 +22,6 @@
#include "fs_lsetxattr.hpp"
#include "fs_path.hpp"
#include "fs_statvfs_cache.hpp"
#include "gidcache.hpp"
#include "num.hpp"
#include "policy_rv.hpp"
#include "str.hpp"
@ -55,10 +54,6 @@ _setxattr_cmd_xattr(const std::string_view &attrname_,
return (fuse_gc1(),0);
if(cmd == "invalidate-all-nodes")
return (fuse_invalidate_all_nodes(),0);
if(cmd == "invalidate-gid-cache")
return (GIDCache::invalidate_all(),0);
if(cmd == "clear-gid-cache")
return (GIDCache::clear_all(),0);
return -ENOATTR;
}
@ -194,8 +189,6 @@ _setxattr(const fuse_req_ctx_t *ctx_,
if(cfg.xattr.to_int())
return -cfg.xattr.to_int();
const ugid::Set ugid(ctx_);
return ::_setxattr(cfg.func.setxattr.policy,
cfg.func.getxattr.policy,
cfg.branches,

1
src/fuse_statfs.cpp

@ -147,7 +147,6 @@ FUSE::statfs(const fuse_req_ctx_t *ctx_,
struct statvfs *st_)
{
const fs::path fusepath{fusepath_};
const ugid::Set ugid(ctx_);
return ::_statfs(cfg.branches,
fusepath,

4
src/fuse_statx_supported.icpp

@ -213,8 +213,6 @@ FUSE::statx(const fuse_req_ctx_t *ctx_,
if(Config::is_ctrl_file(fusepath))
return ::_statx_controlfile(st_);
const ugid::Set ugid(ctx_);
return ::_statx(fusepath,
flags_|AT_STATX_DONT_SYNC,
mask_,
@ -247,8 +245,6 @@ FUSE::statx_fh(const fuse_req_ctx_t *ctx_,
FileInfo *fi = FileInfo::from_fh(fh);
const ugid::Set ugid(ctx_);
return ::_statx(fi->fusepath,
flags_|AT_STATX_DONT_SYNC,
mask_,

64
src/fuse_symlink.cpp

@ -23,7 +23,7 @@
#include "fs_lstat.hpp"
#include "fs_path.hpp"
#include "fs_inode.hpp"
#include "fs_symlink.hpp"
#include "fs_symlink_as.hpp"
#include "fuse_getattr.hpp"
#include "ugid.hpp"
@ -35,7 +35,8 @@
static
int
_symlink_loop_core(const fs::path &newbranch_,
_symlink_loop_core(const ugid_t ugid_,
const fs::path &newbranch_,
const char *target_,
const fs::path &linkpath_,
struct stat *st_)
@ -45,7 +46,7 @@ _symlink_loop_core(const fs::path &newbranch_,
fullnewpath = newbranch_ / linkpath_;
rv = fs::symlink(target_,fullnewpath);
rv = fs::symlink_as(ugid_,target_,fullnewpath);
if((rv >= 0) && (st_ != NULL) && (st_->st_ino == 0))
{
fs::lstat(fullnewpath,st_);
@ -60,7 +61,8 @@ _symlink_loop_core(const fs::path &newbranch_,
static
int
_symlink_loop(const fs::path &existingbranch_,
_symlink_loop(const ugid_t ugid_,
const fs::path &existingbranch_,
const std::vector<Branch*> &newbranches_,
const char *target_,
const fs::path &linkpath_,
@ -72,13 +74,14 @@ _symlink_loop(const fs::path &existingbranch_,
for(auto &newbranch :newbranches_)
{
rv = fs::clonepath_as_root(existingbranch_,
newbranch->path,
newdirpath_);
rv = fs::clonepath(existingbranch_,
newbranch->path,
newdirpath_);
if(rv < 0)
err = rv;
else
err = ::_symlink_loop_core(newbranch->path,
err = ::_symlink_loop_core(ugid_,
newbranch->path,
target_,
linkpath_,
st_);
@ -89,7 +92,8 @@ _symlink_loop(const fs::path &existingbranch_,
static
int
_symlink(const Policy::Search &searchFunc_,
_symlink(const ugid_t ugid_,
const Policy::Search &searchFunc_,
const Policy::Create &createFunc_,
const Branches &branches_,
const char *target_,
@ -111,7 +115,8 @@ _symlink(const Policy::Search &searchFunc_,
if(rv < 0)
return rv;
return ::_symlink_loop(existingbranches[0]->path,
return ::_symlink_loop(ugid_,
existingbranches[0]->path,
newbranches,
target_,
linkpath_,
@ -119,22 +124,6 @@ _symlink(const Policy::Search &searchFunc_,
st_);
}
int
FUSE::symlink(const fuse_req_ctx_t *ctx_,
const char *target_,
const char *linkpath_,
struct stat *st_,
fuse_timeouts_t *timeouts_)
{
const fs::path linkpath{linkpath_};
return FUSE::symlink(ctx_,
target_,
linkpath,
st_,
timeouts_);
}
int
FUSE::symlink(const fuse_req_ctx_t *ctx_,
const char *target_,
@ -143,9 +132,9 @@ FUSE::symlink(const fuse_req_ctx_t *ctx_,
fuse_timeouts_t *timeouts_)
{
int rv;
const ugid::Set ugid(ctx_);
rv = ::_symlink(cfg.func.getattr.policy,
rv = ::_symlink(ctx_,
cfg.func.getattr.policy,
cfg.func.symlink.policy,
cfg.branches,
target_,
@ -154,7 +143,8 @@ FUSE::symlink(const fuse_req_ctx_t *ctx_,
if(rv == -EROFS)
{
cfg.branches.find_and_set_mode_ro();
rv = ::_symlink(cfg.func.getattr.policy,
rv = ::_symlink(ctx_,
cfg.func.getattr.policy,
cfg.func.symlink.policy,
cfg.branches,
target_,
@ -181,3 +171,19 @@ FUSE::symlink(const fuse_req_ctx_t *ctx_,
return rv;
}
int
FUSE::symlink(const fuse_req_ctx_t *ctx_,
const char *target_,
const char *linkpath_,
struct stat *st_,
fuse_timeouts_t *timeouts_)
{
const fs::path linkpath{linkpath_};
return FUSE::symlink(ctx_,
target_,
linkpath,
st_,
timeouts_);
}

3
src/fuse_truncate.cpp

@ -94,8 +94,7 @@ FUSE::truncate(const fuse_req_ctx_t *ctx_,
const char *fusepath_,
off_t size_)
{
const fs::path fusepath{fusepath_};
const ugid::Set ugid(ctx_);
const fs::path fusepath{fusepath_};
return ::_truncate(cfg.func.truncate.policy,
cfg.func.getattr.policy,

3
src/fuse_unlink.cpp

@ -68,8 +68,7 @@ int
FUSE::unlink(const fuse_req_ctx_t *ctx_,
const char *fusepath_)
{
const fs::path fusepath{fusepath_};
const ugid::Set ugid(ctx_);
const fs::path fusepath{fusepath_};
return ::_unlink(cfg.func.unlink.policy,
cfg.branches,

3
src/fuse_utimens.cpp

@ -93,8 +93,7 @@ FUSE::utimens(const fuse_req_ctx_t *ctx_,
const char *fusepath_,
const timespec ts_[2])
{
const fs::path fusepath{fusepath_};
const ugid::Set ugid(ctx_);
const fs::path fusepath{fusepath_};
return ::_utimens(cfg.func.utimens.policy,
cfg.func.getattr.policy,

181
src/gidcache.cpp

@ -1,181 +0,0 @@
/*
Copyright (c) 2016, 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 "gidcache.hpp"
#include "syslog.hpp"
#include <grp.h>
#include <pwd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#if defined __linux__ and UGID_USE_RWLOCK == 0
# include <sys/syscall.h>
#elif __APPLE__
# include <sys/param.h>
#endif
int GIDCache::expire_timeout = (60 * 60);
int GIDCache::remove_timeout = (60 * 60 * 12);
boost::concurrent_flat_map<uid_t,GIDRecord> GIDCache::_records;
static
int
_getgrouplist(const char *user,
const gid_t group,
gid_t *groups,
int *ngroups)
{
#if __APPLE__
return ::getgrouplist(user,group,(int*)groups,ngroups);
#else
return ::getgrouplist(user,group,groups,ngroups);
#endif
}
static
inline
int
_setgroups(const std::vector<gid_t> gids_)
{
if(gids_.empty())
return 0;
#if defined __linux__ and UGID_USE_RWLOCK == 0
# if defined SYS_setgroups32
return ::syscall(SYS_setgroups32,gids_.size(),gids_.data());
# else
return ::syscall(SYS_setgroups,gids_.size(),gids_.data());
# endif
#else
return ::setgroups(gids_.size(),gids_.data());
#endif
}
static
void
_getgroups(const uid_t uid_,
const gid_t gid_,
std::vector<gid_t> &gids_)
{
int rv;
int ngroups;
char buf[4096];
struct passwd pwd;
struct passwd *pwdrv;
gids_.clear();
rv = ::getpwuid_r(uid_,&pwd,buf,sizeof(buf),&pwdrv);
if((rv == -1) || (pwdrv == NULL))
goto error;
ngroups = 0;
rv = ::_getgrouplist(pwd.pw_name,gid_,NULL,&ngroups);
gids_.resize(ngroups);
rv = ::_getgrouplist(pwd.pw_name,gid_,gids_.data(),&ngroups);
if((size_t)ngroups < gids_.size())
gids_.resize(ngroups);
return;
error:
gids_.clear();
//gids_.push_back(gid_);
}
int
GIDCache::initgroups(const uid_t uid_,
const gid_t gid_)
{
auto first_func =
[=](auto &x)
{
x.second.last_update = ::time(NULL);
::_getgroups(uid_,gid_,x.second.gids);
::_setgroups(x.second.gids);
};
auto exists_func =
[=](auto &x)
{
time_t now;
now = ::time(NULL);
if((now - x.second.last_update) > GIDCache::expire_timeout)
{
::_getgroups(uid_,gid_,x.second.gids);
x.second.last_update = now;
}
::_setgroups(x.second.gids);
};
_records.try_emplace_and_visit(uid_,
first_func,
exists_func);
return 0;
}
void
GIDCache::invalidate_all()
{
size_t size;
size = _records.size();
_records.visit_all([](auto &x)
{
x.second.last_update = 0;
});
SysLog::info("gid cache invalidated, {} entries",size);
}
void
GIDCache::clear_all()
{
size_t size;
size = _records.size();
_records.clear();
SysLog::info("gid cache cleared, {} entries",size);
}
void
GIDCache::clear_unused()
{
int erased = 0;
time_t now = ::time(NULL);
auto erase_func =
[now,&erased](auto &x)
{
bool should_erase;
time_t time_delta;
time_delta = (now - x.second.last_update);
should_erase = (time_delta > GIDCache::remove_timeout);
erased += should_erase;
return should_erase;
};
_records.erase_if(erase_func);
SysLog::info("cleared {} unused gid cache entries",erased);
}

50
src/gidcache.hpp

@ -1,50 +0,0 @@
/*
Copyright (c) 2016, 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 "boost/unordered/concurrent_flat_map.hpp"
#include <sys/types.h>
#include <unistd.h>
#include <vector>
struct GIDRecord
{
std::vector<gid_t> gids;
time_t last_update;
};
struct GIDCache
{
public:
static
int
initgroups(const uid_t uid,
const gid_t gid);
static void invalidate_all();
static void clear_all();
static void clear_unused();
public:
static int expire_timeout;
static int remove_timeout;
private:
static boost::concurrent_flat_map<uid_t,GIDRecord> _records;
};

11
src/mergerfs.cpp

@ -18,12 +18,12 @@
#include "mergerfs_fsck.hpp"
#include "mergerfs_collect_info.hpp"
#include "caps.hpp"
#include "config.hpp"
#include "fs_path.hpp"
#include "fs_readahead.hpp"
#include "fs_umount2.hpp"
#include "fs_wait_for_mount.hpp"
#include "gidcache.hpp"
#include "maintenance_thread.hpp"
#include "oom.hpp"
#include "option_parser.hpp"
@ -269,7 +269,6 @@ _usr2_signal_handler(int signal_)
{
// SysLog::info("Received SIGUSR2 - triggering thorough gc");
// fuse_gc();
// GIDCache::clear_all();
}
static
@ -340,12 +339,6 @@ _main(int argc_,
}
::_warn_if_not_root();
MaintenanceThread::push_job([](int count_)
{
if((count_ % 60) == 0)
GIDCache::clear_unused();
});
::_setup_resources(cfg.scheduling_priority);
::_setup_signal_handlers();
::_set_oom_score_adj();
@ -354,6 +347,8 @@ _main(int argc_,
if(cfg.lazy_umount_mountpoint)
::_lazy_umount(cfg.mountpoint);
caps::setup();
rv = fuse_main(args.argc,
args.argv,
&ops);

2
src/policy_ff.cpp

@ -16,6 +16,8 @@
#include "policy_ff.hpp"
#include "ugid.hpp"
#include "errno.hpp"
#include "fs_exists.hpp"
#include "fs_info.hpp"

4
src/predictability.h

@ -0,0 +1,4 @@
#pragma once
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

33
src/ugid.cpp

@ -1,33 +1,8 @@
/*
Copyright (c) 2016, 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 "gidcache.hpp"
#if defined __linux__ and UGID_USE_RWLOCK == 0
#include "ugid_linux.icpp"
#else
#include "ugid_rwlock.icpp"
#endif
#include <unistd.h>
namespace ugid
{
void
initgroups(const uid_t uid_,
const gid_t gid_)
{
GIDCache::initgroups(uid_,gid_);
}
thread_local uid_t currentuid = 0;
thread_local gid_t currentgid = 0;
thread_local bool initialized = false;
}

111
src/ugid.hpp

@ -16,21 +16,112 @@
#pragma once
#include "fuse_req_ctx.h"
#include "predictability.h"
#include "fuse_kernel.h"
#include <assert.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>
namespace ugid
struct ugid_t
{
void init();
void initgroups(const uid_t uid, const gid_t gid);
}
ugid_t(const uid_t uid_,
const gid_t gid_)
: uid(uid_),
gid(gid_)
{
}
ugid_t(const fuse_req_ctx_t *ctx_)
: ugid_t(ctx_->uid,ctx_->gid)
{
}
uid_t uid;
gid_t gid;
};
#if defined SYS_setreuid32
#define SETREUID(R,E) (::syscall(SYS_setreuid32,(R),(E)))
#else
#define SETREUID(R,E) (::syscall(SYS_setreuid,(R),(E)))
#endif
#if defined SYS_setregid32
#define SETREGID(R,E) (::syscall(SYS_setregid32,(R),(E)))
#else
#define SETREGID(R,E) (::syscall(SYS_setregid,(R),(E)))
#endif
#if defined SYS_geteuid32
#define GETEUID() (::syscall(SYS_geteuid32))
#else
#define GETEUID() (::syscall(SYS_geteuid))
#endif
#if defined __linux__ and UGID_USE_RWLOCK == 0
#pragma message "using ugid_linux.hpp"
#include "ugid_linux.hpp"
#if defined SYS_getegid32
#define GETEGID() (::syscall(SYS_getegid32))
#else
#pragma message "using ugid_rwlock.hpp"
#include "ugid_rwlock.hpp"
#define GETEGID() (::syscall(SYS_getegid))
#endif
namespace ugid
{
extern thread_local uid_t currentuid;
extern thread_local gid_t currentgid;
extern thread_local bool initialized;
static
inline
void
set(const uid_t newuid_,
const gid_t newgid_)
{
assert(newuid_ != FUSE_INVALID_UIDGID);
assert(newgid_ != FUSE_INVALID_UIDGID);
if(unlikely(!initialized))
{
currentuid = GETEUID();
currentgid = GETEGID();
initialized = true;
}
if((newuid_ == currentuid) && (newgid_ == currentgid))
return;
SETREGID(-1,newgid_);
SETREUID(-1,newuid_);
currentuid = newuid_;
currentgid = newgid_;
}
struct SetGuard
{
SetGuard(const ugid_t ugid_)
: prev(currentuid,currentgid)
{
assert(currentuid == 0);
assert(currentgid == 0);
ugid::set(ugid_.uid,ugid_.gid);
}
~SetGuard()
{
ugid::set(prev.uid,prev.gid);
}
const ugid_t prev;
};
}
#undef SETREUID
#undef SETREGID
#undef GETEUID
#undef GETEGID

128
src/ugid_linux.hpp

@ -1,128 +0,0 @@
/*
Copyright (c) 2016, 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 "fuse_req_ctx.h"
#include <assert.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <grp.h>
#include <pwd.h>
#include <map>
#include <vector>
#if defined SYS_setreuid32
#define SETREUID(R,E) (::syscall(SYS_setreuid32,(R),(E)))
#else
#define SETREUID(R,E) (::syscall(SYS_setreuid,(R),(E)))
#endif
#if defined SYS_setregid32
#define SETREGID(R,E) (::syscall(SYS_setregid32,(R),(E)))
#else
#define SETREGID(R,E) (::syscall(SYS_setregid,(R),(E)))
#endif
#if defined SYS_geteuid32
#define GETEUID() (::syscall(SYS_geteuid32))
#else
#define GETEUID() (::syscall(SYS_geteuid))
#endif
#if defined SYS_getegid32
#define GETEGID() (::syscall(SYS_getegid32))
#else
#define GETEGID() (::syscall(SYS_getegid))
#endif
namespace ugid
{
extern thread_local uid_t currentuid;
extern thread_local gid_t currentgid;
extern thread_local bool initialized;
struct Set
{
Set(const uid_t newuid_,
const gid_t newgid_)
{
assert((int)newuid_ != -1);
assert((int)newgid_ != -1);
if(!initialized)
{
currentuid = GETEUID();
currentgid = GETEGID();
initialized = true;
}
if((newuid_ == currentuid) && (newgid_ == currentgid))
return;
if(currentuid != 0)
{
SETREUID(-1,0);
SETREGID(-1,0);
}
if(newgid_)
{
SETREGID(-1,newgid_);
ugid::initgroups(newuid_,newgid_);
}
if(newuid_)
SETREUID(-1,newuid_);
currentuid = newuid_;
currentgid = newgid_;
}
Set(const fuse_req_ctx_t *ctx_)
: Set(ctx_->uid,ctx_->gid)
{
}
};
struct SetRootGuard
{
SetRootGuard() :
prevuid(currentuid),
prevgid(currentgid)
{
Set(0,0);
}
~SetRootGuard()
{
Set(prevuid,prevgid);
}
const uid_t prevuid;
const gid_t prevgid;
};
}
#undef SETREUID
#undef SETREGID
#undef GETEUID
#undef GETEGID

34
src/ugid_linux.icpp

@ -1,34 +0,0 @@
/*
Copyright (c) 2016, 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 <stdlib.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>
namespace ugid
{
thread_local uid_t currentuid = 0;
thread_local gid_t currentgid = 0;
thread_local bool initialized = false;
void
init()
{
}
}

106
src/ugid_rwlock.hpp

@ -1,106 +0,0 @@
/*
Copyright (c) 2016, 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 "fuse_req_ctx.h"
#include <pthread.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
namespace ugid
{
extern uid_t currentuid;
extern gid_t currentgid;
extern pthread_rwlock_t rwlock;
static
void
ugid_set(const uid_t newuid_,
const gid_t newgid_)
{
pthread_rwlock_rdlock(&rwlock);
if((newuid_ == currentuid) && (newgid_ == currentgid))
return;
pthread_rwlock_unlock(&rwlock);
pthread_rwlock_wrlock(&rwlock);
if((newuid_ == currentuid) && (newgid_ == currentgid))
return;
if(currentuid != 0)
{
::seteuid(0);
::setegid(0);
}
if(newgid_)
{
::setegid(newgid_);
initgroups(newuid_,newgid_);
}
if(newuid_)
::seteuid(newuid_);
currentuid = newuid_;
currentgid = newgid_;
}
struct Set
{
Set(const uid_t newuid_,
const gid_t newgid_)
{
ugid_set(newuid_,newgid_);
}
Set(const fuse_req_ctx_t *ctx_)
: Set(ctx_->uid,ctx_->gid)
{
}
~Set()
{
pthread_rwlock_unlock(&rwlock);
}
};
struct SetRootGuard
{
SetRootGuard() :
prevuid(currentuid),
prevgid(currentgid)
{
pthread_rwlock_unlock(&rwlock);
ugid_set(0,0);
}
~SetRootGuard()
{
pthread_rwlock_unlock(&rwlock);
ugid_set(prevuid,prevgid);
}
const uid_t prevuid;
const gid_t prevgid;
};
}

45
src/ugid_rwlock.icpp

@ -1,45 +0,0 @@
/*
Copyright (c) 2016, 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 <pthread.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <vector>
namespace ugid
{
uid_t currentuid;
gid_t currentgid;
pthread_rwlock_t rwlock;
void
init()
{
pthread_rwlockattr_t attr;
pthread_rwlockattr_init(&attr);
# if defined PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP
pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
# endif
pthread_rwlock_init(&rwlock,&attr);
currentuid = ::geteuid();
currentgid = ::getegid();
}
}
Loading…
Cancel
Save