mirror of
https://github.com/trapexit/mergerfs.git
synced 2026-10-26 02:48:59 -04:00
Update the secondary group cache (#1492)
* Use concurrent_flat_map to build a global cache rather than per thread. * Create global maintanence thread manager for clearing unused cache entries.
This commit is contained in:
parent
9e626228be
commit
f852474073
17 changed files with 342 additions and 246 deletions
|
|
@ -56,7 +56,8 @@ SRC_CPP = \
|
||||||
lib/fuse_msgbuf.cpp \
|
lib/fuse_msgbuf.cpp \
|
||||||
lib/pin_threads.cpp \
|
lib/pin_threads.cpp \
|
||||||
lib/format.cpp \
|
lib/format.cpp \
|
||||||
lib/node.cpp
|
lib/node.cpp \
|
||||||
|
lib/maintenance_thread.cpp
|
||||||
|
|
||||||
OBJS_C = $(SRC_C:lib/%.c=build/%.o)
|
OBJS_C = $(SRC_C:lib/%.c=build/%.o)
|
||||||
OBJS_CPP = $(SRC_CPP:lib/%.cpp=build/%.o)
|
OBJS_CPP = $(SRC_CPP:lib/%.cpp=build/%.o)
|
||||||
|
|
|
||||||
|
|
@ -688,8 +688,7 @@ int fuse_is_lib_option(const char *opt);
|
||||||
*/
|
*/
|
||||||
int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, size_t op_size);
|
int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op, size_t op_size);
|
||||||
|
|
||||||
int fuse_start_maintenance_thread(struct fuse *fuse);
|
void fuse_populate_maintenance_thread(struct fuse *fuse);
|
||||||
void fuse_stop_maintenance_thread(struct fuse *fuse);
|
|
||||||
|
|
||||||
int fuse_log_metrics_get(void);
|
int fuse_log_metrics_get(void);
|
||||||
void fuse_log_metrics_set(int enabled);
|
void fuse_log_metrics_set(int enabled);
|
||||||
|
|
|
||||||
13
libfuse/include/maintenance_thread.hpp
Normal file
13
libfuse/include/maintenance_thread.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
class MaintenanceThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void setup();
|
||||||
|
static void stop();
|
||||||
|
static void push_job(const std::function<void(int)> &func);
|
||||||
|
};
|
||||||
|
|
@ -27,8 +27,10 @@
|
||||||
#include "fuse_pollhandle.h"
|
#include "fuse_pollhandle.h"
|
||||||
#include "fuse_msgbuf.hpp"
|
#include "fuse_msgbuf.hpp"
|
||||||
|
|
||||||
#include <vector>
|
#include "maintenance_thread.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
|
@ -141,7 +143,6 @@ struct fuse
|
||||||
struct fuse_fs *fs;
|
struct fuse_fs *fs;
|
||||||
struct lock_queue_element *lockq;
|
struct lock_queue_element *lockq;
|
||||||
|
|
||||||
pthread_t maintenance_thread;
|
|
||||||
kvec_t(remembered_node_t) remembered_nodes;
|
kvec_t(remembered_node_t) remembered_nodes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -4032,49 +4033,26 @@ fuse_gc1()
|
||||||
fuse_malloc_trim();
|
fuse_malloc_trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
|
||||||
void*
|
|
||||||
fuse_maintenance_loop(void *fuse_)
|
|
||||||
{
|
|
||||||
int loops;
|
|
||||||
int sleep_time;
|
|
||||||
struct fuse *f = (struct fuse*)fuse_;
|
|
||||||
|
|
||||||
pthread_setname_np(pthread_self(),"fuse.maint");
|
|
||||||
|
|
||||||
loops = 0;
|
|
||||||
sleep_time = 60;
|
|
||||||
while(1)
|
|
||||||
{
|
|
||||||
if(remember_nodes(f))
|
|
||||||
fuse_prune_remembered_nodes(f);
|
|
||||||
|
|
||||||
if((loops % 15) == 0)
|
|
||||||
fuse_gc1();
|
|
||||||
|
|
||||||
if(g_LOG_METRICS)
|
|
||||||
metrics_log_nodes_info_to_tmp_dir(f);
|
|
||||||
|
|
||||||
loops++;
|
|
||||||
sleep(sleep_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
fuse_start_maintenance_thread(struct fuse *f_)
|
|
||||||
{
|
|
||||||
return fuse_start_thread(&f_->maintenance_thread,fuse_maintenance_loop,f_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
fuse_stop_maintenance_thread(struct fuse *f_)
|
fuse_populate_maintenance_thread(struct fuse *f_)
|
||||||
{
|
{
|
||||||
mutex_lock(&f_->lock);
|
MaintenanceThread::push_job([=](int count_)
|
||||||
pthread_cancel(f_->maintenance_thread);
|
{
|
||||||
mutex_unlock(&f_->lock);
|
if(remember_nodes(f_))
|
||||||
pthread_join(f_->maintenance_thread,NULL);
|
fuse_prune_remembered_nodes(f_);
|
||||||
|
});
|
||||||
|
|
||||||
|
MaintenanceThread::push_job([](int count_)
|
||||||
|
{
|
||||||
|
if((count_ % 15) == 0)
|
||||||
|
fuse_gc1();
|
||||||
|
});
|
||||||
|
|
||||||
|
MaintenanceThread::push_job([=](int count_)
|
||||||
|
{
|
||||||
|
if(g_LOG_METRICS)
|
||||||
|
metrics_log_nodes_info_to_tmp_dir(f_);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
struct fuse*
|
struct fuse*
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
#include "scope_guard.hpp"
|
#include "scope_guard.hpp"
|
||||||
#include "thread_pool.hpp"
|
#include "thread_pool.hpp"
|
||||||
#include "syslog.hpp"
|
#include "syslog.hpp"
|
||||||
|
#include "maintenance_thread.hpp"
|
||||||
|
|
||||||
#include "fuse_i.h"
|
#include "fuse_i.h"
|
||||||
#include "fuse_kernel.h"
|
#include "fuse_kernel.h"
|
||||||
|
|
@ -317,7 +318,7 @@ fuse_session_loop_mt(struct fuse_session *se_,
|
||||||
pin_threads_type_);
|
pin_threads_type_);
|
||||||
|
|
||||||
while(!fuse_session_exited(se_))
|
while(!fuse_session_exited(se_))
|
||||||
sem_wait(&finished);
|
sem_wait(&finished);
|
||||||
|
|
||||||
sem_destroy(&finished);
|
sem_destroy(&finished);
|
||||||
|
|
||||||
|
|
@ -325,22 +326,23 @@ fuse_session_loop_mt(struct fuse_session *se_,
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
fuse_loop_mt(struct fuse *f)
|
fuse_loop_mt(struct fuse *f_)
|
||||||
{
|
{
|
||||||
if(f == NULL)
|
int res;
|
||||||
|
|
||||||
|
if(f_ == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
int res = fuse_start_maintenance_thread(f);
|
MaintenanceThread::setup();
|
||||||
if(res)
|
fuse_populate_maintenance_thread(f_);
|
||||||
return -1;
|
|
||||||
|
|
||||||
res = fuse_session_loop_mt(fuse_get_session(f),
|
res = fuse_session_loop_mt(fuse_get_session(f_),
|
||||||
fuse_config_get_read_thread_count(),
|
fuse_config_get_read_thread_count(),
|
||||||
fuse_config_get_process_thread_count(),
|
fuse_config_get_process_thread_count(),
|
||||||
fuse_config_get_process_thread_queue_depth(),
|
fuse_config_get_process_thread_queue_depth(),
|
||||||
fuse_config_get_pin_threads());
|
fuse_config_get_pin_threads());
|
||||||
|
|
||||||
fuse_stop_maintenance_thread(f);
|
MaintenanceThread::stop();
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
65
libfuse/lib/maintenance_thread.cpp
Normal file
65
libfuse/lib/maintenance_thread.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#include "maintenance_thread.hpp"
|
||||||
|
|
||||||
|
#include "fmt/core.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
|
||||||
|
pthread_t g_thread;
|
||||||
|
std::vector<std::function<void(int)>> g_funcs;
|
||||||
|
std::mutex g_mutex;
|
||||||
|
|
||||||
|
static
|
||||||
|
void*
|
||||||
|
_thread_loop(void *)
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
|
||||||
|
pthread_setname_np(pthread_self(),"fuse.maint");
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lg(g_mutex);
|
||||||
|
for(auto &func : g_funcs)
|
||||||
|
func(count);
|
||||||
|
}
|
||||||
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
::sleep(60);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MaintenanceThread::setup()
|
||||||
|
{
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
rv = pthread_create(&g_thread,NULL,_thread_loop,NULL);
|
||||||
|
assert((rv == 0) && "pthread_create failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MaintenanceThread::push_job(const std::function<void(int)> &func_)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lg(g_mutex);
|
||||||
|
|
||||||
|
g_funcs.emplace_back(func_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
MaintenanceThread::stop()
|
||||||
|
{
|
||||||
|
pthread_cancel(g_thread);
|
||||||
|
pthread_join(g_thread,NULL);
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ background](../intro_to_filesystems.md) in such things is recommended
|
||||||
for more advanced configurations.
|
for more advanced configurations.
|
||||||
|
|
||||||
These option names and values are the same regardless of whether you
|
These option names and values are the same regardless of whether you
|
||||||
use them with the `mergerfs` commandline program, in fstab, or in a
|
use them with the `mergerfs` command line program, in fstab, or in a
|
||||||
config file.
|
config file.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -204,6 +204,12 @@ config file.
|
||||||
* **[passthrough](passthrough.md)**: Enable [FUSE IO
|
* **[passthrough](passthrough.md)**: Enable [FUSE IO
|
||||||
passthrough](https://kernelnewbies.org/Linux_6.9#Faster_FUSE_I.2FO)
|
passthrough](https://kernelnewbies.org/Linux_6.9#Faster_FUSE_I.2FO)
|
||||||
if available. (default: off)
|
if available. (default: off)
|
||||||
|
* **gid-cache-expire-timeout**: 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**: Number of seconds to wait till cached
|
||||||
|
data is removed due to lack of usage. (default: 43200)
|
||||||
|
|
||||||
**NOTE:** Options are evaluated in the order listed so if the options
|
**NOTE:** Options are evaluated in the order listed so if the options
|
||||||
are **func.rmdir=rand,category.action=ff** the **action** category
|
are **func.rmdir=rand,category.action=ff** the **action** category
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,26 @@
|
||||||
|
|
||||||
### Supplemental user groups
|
### Supplemental user groups
|
||||||
|
|
||||||
|
#### Supplemental group caching
|
||||||
|
|
||||||
Due to the overhead of
|
Due to the overhead of
|
||||||
[getgroups/setgroups](http://linux.die.net/man/2/setgroups) mergerfs
|
[getgroups/setgroups](http://linux.die.net/man/2/setgroups) mergerfs
|
||||||
utilizes a cache. This cache is opportunistic and per thread. Each
|
utilizes a cache. As necessary the supplemental group information will
|
||||||
thread will query the supplemental groups for a user when that
|
be queried and cached. That cached list of groups will be used to set
|
||||||
particular thread needs to change credentials and will keep that data
|
the supplement groups as necessary. Due to the high cost of querying
|
||||||
for the lifetime of the thread. This means that if a user is added to
|
the group list the default expiry for said data is 1 hour and after 12
|
||||||
a group it may not be picked up without the restart of
|
hours of no usage will be removed from the cache all together.
|
||||||
mergerfs. In the future this may be improved to allow a periodic or
|
|
||||||
manual clearing of the cache.
|
#### Host vs Container identity
|
||||||
|
|
||||||
While not a bug some users have found when using containers that
|
While not a bug some users have found when using containers that
|
||||||
supplemental groups defined inside the container don't work as
|
supplemental groups defined inside the container don't work as
|
||||||
expected. Since mergerfs lives outside the container it is querying
|
expected. Since mergerfs lives outside the container it is querying
|
||||||
the host's group database. Effectively containers have their own user
|
the host's group database. Containers have their own user and group
|
||||||
and group definitions unless setup otherwise just as different systems
|
definitions unless setup otherwise just as different systems would.
|
||||||
would.
|
|
||||||
|
|
||||||
Users should mount in the host group file into the containers or use a
|
Users should mount in the host group file into the containers or use a
|
||||||
standard shared user & groups technology like NIS or LDAP.
|
standard shared user and groups technology like NIS or LDAP.
|
||||||
|
|
||||||
|
|
||||||
### directory mtime is not being updated
|
### directory mtime is not being updated
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,8 @@ Config::Config()
|
||||||
fsname(),
|
fsname(),
|
||||||
func(),
|
func(),
|
||||||
fuse_msg_size("1M"),
|
fuse_msg_size("1M"),
|
||||||
|
gid_cache_expire_timeout(60 * 60),
|
||||||
|
gid_cache_remove_timeout(60 * 60 * 12),
|
||||||
ignorepponrename(false),
|
ignorepponrename(false),
|
||||||
inodecalc("hybrid-hash"),
|
inodecalc("hybrid-hash"),
|
||||||
lazy_umount_mountpoint(false),
|
lazy_umount_mountpoint(false),
|
||||||
|
|
@ -184,6 +186,8 @@ Config::Config()
|
||||||
_map["func.unlink"] = &func.unlink;
|
_map["func.unlink"] = &func.unlink;
|
||||||
_map["func.utimens"] = &func.utimens;
|
_map["func.utimens"] = &func.utimens;
|
||||||
_map["fuse_msg_size"] = &fuse_msg_size;
|
_map["fuse_msg_size"] = &fuse_msg_size;
|
||||||
|
_map["gid-cache-expire-timeout"] = &gid_cache_expire_timeout;
|
||||||
|
_map["gid-cache-remove-timeout"] = &gid_cache_remove_timeout;
|
||||||
_map["handle-killpriv"] = &handle_killpriv;
|
_map["handle-killpriv"] = &handle_killpriv;
|
||||||
_map["handle-killpriv-v2"] = &handle_killpriv_v2;
|
_map["handle-killpriv-v2"] = &handle_killpriv_v2;
|
||||||
_map["ignorepponrename"] = &ignorepponrename;
|
_map["ignorepponrename"] = &ignorepponrename;
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
#include "config_cachefiles.hpp"
|
#include "config_cachefiles.hpp"
|
||||||
#include "config_flushonclose.hpp"
|
#include "config_flushonclose.hpp"
|
||||||
#include "config_follow_symlinks.hpp"
|
#include "config_follow_symlinks.hpp"
|
||||||
|
#include "config_gidcache.hpp"
|
||||||
#include "config_inodecalc.hpp"
|
#include "config_inodecalc.hpp"
|
||||||
#include "config_link_exdev.hpp"
|
#include "config_link_exdev.hpp"
|
||||||
#include "config_log_metrics.hpp"
|
#include "config_log_metrics.hpp"
|
||||||
|
|
@ -130,6 +131,8 @@ public:
|
||||||
ConfigSTR fsname;
|
ConfigSTR fsname;
|
||||||
Funcs func;
|
Funcs func;
|
||||||
ConfigPageSize fuse_msg_size;
|
ConfigPageSize fuse_msg_size;
|
||||||
|
GIDCacheExpireTimeout gid_cache_expire_timeout;
|
||||||
|
GIDCacheRemoveTimeout gid_cache_remove_timeout;
|
||||||
ConfigBOOL handle_killpriv = true;
|
ConfigBOOL handle_killpriv = true;
|
||||||
ConfigBOOL handle_killpriv_v2 = true;
|
ConfigBOOL handle_killpriv_v2 = true;
|
||||||
ConfigBOOL ignorepponrename;
|
ConfigBOOL ignorepponrename;
|
||||||
|
|
|
||||||
65
src/config_gidcache.cpp
Normal file
65
src/config_gidcache.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#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 &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 &s_)
|
||||||
|
{
|
||||||
|
int rv;
|
||||||
|
|
||||||
|
rv = str::from(s_,&GIDCache::remove_timeout);
|
||||||
|
if(rv < 0)
|
||||||
|
return rv;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
25
src/config_gidcache.hpp
Normal file
25
src/config_gidcache.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#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 &) 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 &) final;
|
||||||
|
};
|
||||||
|
|
@ -336,7 +336,7 @@ namespace l
|
||||||
fuse_invalidate_all_nodes();
|
fuse_invalidate_all_nodes();
|
||||||
return 0;
|
return 0;
|
||||||
case IOCTL_INVALIDATE_GID_CACHE:
|
case IOCTL_INVALIDATE_GID_CACHE:
|
||||||
GIDCache::invalidate_all_caches();
|
GIDCache::invalidate_all();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
239
src/gidcache.cpp
239
src/gidcache.cpp
|
|
@ -14,7 +14,7 @@
|
||||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "rnd.hpp"
|
#include "gidcache.hpp"
|
||||||
|
|
||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
|
|
@ -28,94 +28,9 @@
|
||||||
# include <sys/param.h>
|
# include <sys/param.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <cassert>
|
int GIDCache::expire_timeout = (60 * 60);
|
||||||
#include <cstdlib>
|
int GIDCache::remove_timeout = (60 * 60 * 12);
|
||||||
#include <algorithm>
|
boost::concurrent_flat_map<uid_t,GIDRecord> GIDCache::_records;
|
||||||
#include <unordered_map>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include "gidcache.hpp"
|
|
||||||
|
|
||||||
std::mutex g_REGISTERED_CACHES_MUTEX;
|
|
||||||
std::unordered_map<pthread_t,GIDCache*> g_REGISTERED_CACHES;
|
|
||||||
|
|
||||||
|
|
||||||
inline
|
|
||||||
bool
|
|
||||||
GIDRecord::operator<(const struct GIDRecord &b) const
|
|
||||||
{
|
|
||||||
return uid < b.uid;
|
|
||||||
}
|
|
||||||
|
|
||||||
GIDCache::GIDCache()
|
|
||||||
: invalidate(false),
|
|
||||||
size(0),
|
|
||||||
recs()
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> guard(g_REGISTERED_CACHES_MUTEX);
|
|
||||||
|
|
||||||
bool inserted;
|
|
||||||
pthread_t pthid;
|
|
||||||
|
|
||||||
pthid = pthread_self();
|
|
||||||
inserted = g_REGISTERED_CACHES.emplace(pthid,this).second;
|
|
||||||
|
|
||||||
assert(inserted == true);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline
|
|
||||||
GIDRecord *
|
|
||||||
GIDCache::begin(void)
|
|
||||||
{
|
|
||||||
return &recs[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline
|
|
||||||
GIDRecord *
|
|
||||||
GIDCache::end(void)
|
|
||||||
{
|
|
||||||
return &recs[size];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline
|
|
||||||
GIDRecord *
|
|
||||||
GIDCache::allocrec(void)
|
|
||||||
{
|
|
||||||
if(size == MAXRECS)
|
|
||||||
return &recs[RND::rand64(MAXRECS)];
|
|
||||||
else
|
|
||||||
return &recs[size++];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline
|
|
||||||
GIDRecord *
|
|
||||||
GIDCache::lower_bound(GIDRecord *begin,
|
|
||||||
GIDRecord *end,
|
|
||||||
const uid_t uid)
|
|
||||||
{
|
|
||||||
int step;
|
|
||||||
int count;
|
|
||||||
GIDRecord *iter;
|
|
||||||
|
|
||||||
count = std::distance(begin,end);
|
|
||||||
while(count > 0)
|
|
||||||
{
|
|
||||||
iter = begin;
|
|
||||||
step = count / 2;
|
|
||||||
std::advance(iter,step);
|
|
||||||
if(iter->uid < uid)
|
|
||||||
{
|
|
||||||
begin = ++iter;
|
|
||||||
count -= step + 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
count = step;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return begin;
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
static
|
||||||
int
|
int
|
||||||
|
|
@ -131,85 +46,111 @@ _getgrouplist(const char *user,
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
GIDRecord *
|
|
||||||
GIDCache::cache(const uid_t uid,
|
|
||||||
const gid_t gid)
|
|
||||||
{
|
|
||||||
int rv;
|
|
||||||
char buf[4096];
|
|
||||||
struct passwd pwd;
|
|
||||||
struct passwd *pwdrv;
|
|
||||||
GIDRecord *rec;
|
|
||||||
|
|
||||||
rec = allocrec();
|
|
||||||
|
|
||||||
rec->uid = uid;
|
|
||||||
rv = ::getpwuid_r(uid,&pwd,buf,sizeof(buf),&pwdrv);
|
|
||||||
if(pwdrv != NULL && rv == 0)
|
|
||||||
{
|
|
||||||
rec->size = 0;
|
|
||||||
::_getgrouplist(pwd.pw_name,gid,NULL,&rec->size);
|
|
||||||
rec->size = std::min(MAXGIDS,rec->size);
|
|
||||||
rv = ::_getgrouplist(pwd.pw_name,gid,rec->gids,&rec->size);
|
|
||||||
if(rv == -1)
|
|
||||||
{
|
|
||||||
rec->gids[0] = gid;
|
|
||||||
rec->size = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rec;
|
|
||||||
}
|
|
||||||
|
|
||||||
static
|
static
|
||||||
inline
|
inline
|
||||||
int
|
int
|
||||||
setgroups(const GIDRecord *rec)
|
_setgroups(const std::vector<gid_t> gids_)
|
||||||
{
|
{
|
||||||
#if defined __linux__ and UGID_USE_RWLOCK == 0
|
#if defined __linux__ and UGID_USE_RWLOCK == 0
|
||||||
# if defined SYS_setgroups32
|
# if defined SYS_setgroups32
|
||||||
return ::syscall(SYS_setgroups32,rec->size,rec->gids);
|
return ::syscall(SYS_setgroups32,gids_.size(),gids_.data());
|
||||||
# else
|
# else
|
||||||
return ::syscall(SYS_setgroups,rec->size,rec->gids);
|
return ::syscall(SYS_setgroups,gids_.size(),gids_.data());
|
||||||
# endif
|
# endif
|
||||||
#else
|
#else
|
||||||
return ::setgroups(rec->size,rec->gids);
|
return ::setgroups(gids_.size(),gids_.data());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
static
|
||||||
GIDCache::initgroups(const uid_t uid,
|
void
|
||||||
const gid_t gid)
|
_getgroups(const uid_t uid_,
|
||||||
|
const gid_t gid_,
|
||||||
|
std::vector<gid_t> &gids_)
|
||||||
{
|
{
|
||||||
int rv;
|
int rv;
|
||||||
GIDRecord *rec;
|
int ngroups;
|
||||||
|
char buf[4096];
|
||||||
|
struct passwd pwd;
|
||||||
|
struct passwd *pwdrv;
|
||||||
|
|
||||||
if(invalidate)
|
gids_.clear();
|
||||||
{
|
rv = ::getpwuid_r(uid_,&pwd,buf,sizeof(buf),&pwdrv);
|
||||||
size = 0;
|
if((rv == -1) || (pwdrv == NULL))
|
||||||
invalidate = false;
|
goto error;
|
||||||
}
|
|
||||||
|
|
||||||
rec = lower_bound(begin(),end(),uid);
|
ngroups = 0;
|
||||||
if(rec == end() || rec->uid != uid)
|
rv = ::_getgrouplist(pwd.pw_name,gid_,NULL,&ngroups);
|
||||||
{
|
gids_.resize(ngroups);
|
||||||
rec = cache(uid,gid);
|
|
||||||
rv = ::setgroups(rec);
|
|
||||||
std::sort(begin(),end());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rv = ::setgroups(rec);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv;
|
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
|
void
|
||||||
GIDCache::invalidate_all_caches()
|
GIDCache::invalidate_all()
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(g_REGISTERED_CACHES_MUTEX);
|
_records.visit_all([](auto &x)
|
||||||
|
{
|
||||||
for(auto &p : g_REGISTERED_CACHES)
|
x.second.last_update = 0;
|
||||||
p.second->invalidate = true;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GIDCache::clear_all()
|
||||||
|
{
|
||||||
|
_records.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
GIDCache::clear_unused()
|
||||||
|
{
|
||||||
|
time_t now = ::time(NULL);
|
||||||
|
auto erase_func =
|
||||||
|
[=](auto &x)
|
||||||
|
{
|
||||||
|
return ((now - x.second.last_update) > GIDCache::remove_timeout);
|
||||||
|
};
|
||||||
|
|
||||||
|
_records.erase_if(erase_func);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,13 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "boost/unordered/concurrent_flat_map.hpp"
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#define MAXGIDS 32
|
#define MAXGIDS 32
|
||||||
#define MAXRECS 256
|
#define MAXRECS 256
|
||||||
|
|
@ -32,42 +35,28 @@
|
||||||
// told to invalidate the cache on demand. A second instance on the
|
// told to invalidate the cache on demand. A second instance on the
|
||||||
// same thread will cause an assert to be triggered.
|
// same thread will cause an assert to be triggered.
|
||||||
|
|
||||||
|
|
||||||
struct GIDRecord
|
struct GIDRecord
|
||||||
{
|
{
|
||||||
uid_t uid;
|
std::vector<gid_t> gids;
|
||||||
int size;
|
time_t last_update;
|
||||||
gid_t gids[MAXGIDS];
|
|
||||||
|
|
||||||
bool
|
|
||||||
operator<(const struct GIDRecord &b) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GIDCache
|
struct GIDCache
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GIDCache();
|
static
|
||||||
|
|
||||||
public:
|
|
||||||
bool invalidate;
|
|
||||||
size_t size;
|
|
||||||
std::array<GIDRecord,MAXRECS> recs;
|
|
||||||
|
|
||||||
private:
|
|
||||||
GIDRecord *begin(void);
|
|
||||||
GIDRecord *end(void);
|
|
||||||
GIDRecord *allocrec(void);
|
|
||||||
GIDRecord *lower_bound(GIDRecord *begin,
|
|
||||||
GIDRecord *end,
|
|
||||||
const uid_t uid);
|
|
||||||
GIDRecord *cache(const uid_t uid,
|
|
||||||
const gid_t gid);
|
|
||||||
|
|
||||||
public:
|
|
||||||
int
|
int
|
||||||
initgroups(const uid_t uid,
|
initgroups(const uid_t uid,
|
||||||
const gid_t gid);
|
const gid_t gid);
|
||||||
|
|
||||||
|
static void invalidate_all();
|
||||||
|
static void clear_all();
|
||||||
|
static void clear_unused();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static void invalidate_all_caches();
|
static int expire_timeout;
|
||||||
|
static int remove_timeout;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static boost::concurrent_flat_map<uid_t,GIDRecord> _records;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
#include "strvec.hpp"
|
#include "strvec.hpp"
|
||||||
#include "syslog.hpp"
|
#include "syslog.hpp"
|
||||||
#include "version.hpp"
|
#include "version.hpp"
|
||||||
|
#include "maintenance_thread.hpp"
|
||||||
|
|
||||||
#include "fuse_access.hpp"
|
#include "fuse_access.hpp"
|
||||||
#include "fuse_bmap.hpp"
|
#include "fuse_bmap.hpp"
|
||||||
|
|
@ -246,7 +247,7 @@ namespace l
|
||||||
{
|
{
|
||||||
SysLog::info("Received SIGUSR2 - triggering thorough gc");
|
SysLog::info("Received SIGUSR2 - triggering thorough gc");
|
||||||
fuse_gc();
|
fuse_gc();
|
||||||
GIDCache::invalidate_all_caches();
|
GIDCache::clear_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
|
|
@ -310,6 +311,11 @@ namespace l
|
||||||
|
|
||||||
l::warn_if_not_root();
|
l::warn_if_not_root();
|
||||||
|
|
||||||
|
MaintenanceThread::push_job([](int count_)
|
||||||
|
{
|
||||||
|
if((count_ % 60) == 0)
|
||||||
|
GIDCache::clear_unused();
|
||||||
|
});
|
||||||
l::setup_resources(cfg->scheduling_priority);
|
l::setup_resources(cfg->scheduling_priority);
|
||||||
l::setup_signal_handlers();
|
l::setup_signal_handlers();
|
||||||
l::get_fuse_operations(ops,cfg->nullrw);
|
l::get_fuse_operations(ops,cfg->nullrw);
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,6 @@ namespace ugid
|
||||||
initgroups(const uid_t uid_,
|
initgroups(const uid_t uid_,
|
||||||
const gid_t gid_)
|
const gid_t gid_)
|
||||||
{
|
{
|
||||||
static thread_local GIDCache cache;
|
GIDCache::initgroups(uid_,gid_);
|
||||||
|
|
||||||
cache.initgroups(uid_,gid_);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue