Browse Source

rewrite gid cache system

Uses fixed storage so as to not require C++11 local thread storage or memory allocation.
pull/155/head
Antonio SJ Musumeci 9 years ago
parent
commit
40f569be37
  1. 7
      README.md
  2. 143
      src/gidcache.cpp
  3. 66
      src/gidcache.hpp
  4. 48
      src/ugid.cpp

7
README.md

@ -1,6 +1,6 @@
% mergerfs(1) mergerfs user manual % mergerfs(1) mergerfs user manual
% Antonio SJ Musumeci <trapexit@spawn.link> % Antonio SJ Musumeci <trapexit@spawn.link>
% 2015-09-14
% 2015-10-11
# NAME # NAME
@ -14,7 +14,7 @@ mergerfs -o&lt;options&gt; &lt;srcpoints&gt; &lt;mountpoint&gt;
**mergerfs** is similar to **mhddfs**, **unionfs**, and **aufs**. Like **mhddfs** in that it too uses **FUSE**. Like **aufs** in that it provides multiple policies for how to handle behavior. **mergerfs** is similar to **mhddfs**, **unionfs**, and **aufs**. Like **mhddfs** in that it too uses **FUSE**. Like **aufs** in that it provides multiple policies for how to handle behavior.
Why **mergerfs** when those exist? **mhddfs** has not been updated in some time nor very flexible. There are also security issues when with running as root. **aufs** is more flexible than **mhddfs** but kernel based and difficult to debug when problems arise and slower to evolve as a result. Neither support file attributes ([chattr](http://linux.die.net/man/1/chattr)).
Why **mergerfs** when those exist? **mhddfs** has not been updated in some time nor very flexible. There are also security issues when with running as root. **aufs** is more flexible than **mhddfs** but kernel based and difficult to debug when problems arise. Neither support file attributes ([chattr](http://linux.die.net/man/1/chattr)).
# FEATURES # FEATURES
@ -288,7 +288,8 @@ A B C
# Known Issues / Bugs # Known Issues / Bugs
* Due to the overhead of [getgroups/setgroups](http://linux.die.net/man/2/setgroups) mergerfs utilizes a cache. This cache is opportunistic and per thread. This means each thread will query the supplemental groups for a user when that particular thread needs to change credentials and currently it will keep that data for the lifetime of the mount or thread. This means that if a user is added to a group it may not be picked up without the restart of mergerfs. However, since the high level FUSE API's (at least the standard version) thread pool dynamically grows and shrinks it's possible that over time a thread will be killed and later a new thread with no cache will start and query the new data.
* Due to the overhead of [getgroups/setgroups](http://linux.die.net/man/2/setgroups) mergerfs utilizes a cache. This cache is opportunistic and per thread. Each thread will query the supplemental groups for a user when that particular thread needs to change credentials and will keep that data for the lifetime of the mount or thread. This means that if a user is added to a group it may not be picked up without the restart of mergerfs. However, since the high level FUSE API's (at least the standard version) thread pool dynamically grows and shrinks it's possible that over time a thread will be killed and later a new thread with no cache will start and query the new data.
* The gid cache uses fixed storage to simplify the design and be compatible with older systems which may not have C++11 compilers (as the original design required). There is enough storage for 256 users' supplemental groups. Each user is allowed upto 32 supplemental groups. Linux >= 2.6.3 allows upto 65535 groups per user but most other *nixs allow far less. NFS allowing only 16. The system does handle overflow gracefully. If the user has more than 32 supplemental groups only the first 32 will be used. If more than 256 users are using the system when an uncached user is found it will evict an existing user's cache at random. So long as there aren't more than 256 active users this should be fine. If either value is too low for your needs you will have to modify `gidcache.hpp` to increase the values. Note that doing so will increase the memory needed by each thread.
# FAQ # FAQ

143
src/gidcache.cpp

@ -0,0 +1,143 @@
/*
The MIT License (MIT)
Copyright (c) 2015 Antonio SJ Musumeci <trapexit@spawn.link>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <stdlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <cstdlib>
#include <algorithm>
#include "gidcache.hpp"
bool
gid_t_rec::operator<(const struct gid_t_rec &b) const
{
return uid < b.uid;
}
inline
gid_t_rec *
gid_t_cache::begin(void)
{
return recs;
}
inline
gid_t_rec *
gid_t_cache::end(void)
{
return recs + size;
}
inline
gid_t_rec *
gid_t_cache::allocrec(void)
{
if(size == MAXRECS)
return &recs[rand() % MAXRECS];
else
return &recs[size++];
}
inline
gid_t_rec *
gid_t_cache::lower_bound(gid_t_rec *begin,
gid_t_rec *end,
const uid_t uid)
{
int step;
int count;
gid_t_rec *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;
}
gid_t_rec *
gid_t_cache::cache(const uid_t uid,
const gid_t gid)
{
int rv;
char buf[4096];
struct passwd pwd;
struct passwd *pwdrv;
gid_t_rec *rec;
rec = allocrec();
rec->uid = uid;
rv = ::getpwuid_r(uid,&pwd,buf,sizeof(buf),&pwdrv);
if(pwdrv != NULL && rv == 0)
{
rec->size = 0;
rv = ::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;
}
void
gid_t_cache::initgroups(const uid_t uid,
const gid_t gid)
{
gid_t_rec *rec;
rec = lower_bound(begin(),end(),uid);
if(rec == end() || rec->uid != uid)
{
rec = cache(uid,gid);
::setgroups(rec->size,rec->gids);
std::sort(begin(),end());
}
else
{
::setgroups(rec->size,rec->gids);
}
}

66
src/gidcache.hpp

@ -0,0 +1,66 @@
/*
The MIT License (MIT)
Copyright (c) 2015 Antonio SJ Musumeci <trapexit@spawn.link>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef __GIDCACHE_HPP__
#define __GIDCACHE_HPP__
#include <sys/types.h>
#include <unistd.h>
#define MAXGIDS 32
#define MAXRECS 256
struct gid_t_rec
{
uid_t uid;
int size;
gid_t gids[MAXGIDS];
bool
operator<(const struct gid_t_rec &b) const;
};
struct gid_t_cache
{
public:
size_t size;
gid_t_rec recs[MAXRECS];
private:
gid_t_rec * begin(void);
gid_t_rec * end(void);
gid_t_rec * allocrec(void);
gid_t_rec * lower_bound(gid_t_rec *begin,
gid_t_rec *end,
const uid_t uid);
gid_t_rec * cache(const uid_t uid,
const gid_t gid);
public:
void
initgroups(const uid_t uid,
const gid_t gid);
};
#endif

48
src/ugid.cpp

@ -1,7 +1,7 @@
/* /*
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014 Antonio SJ Musumeci <trapexit@spawn.link>
Copyright (c) 2015 Antonio SJ Musumeci <trapexit@spawn.link>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -22,17 +22,7 @@
THE SOFTWARE. THE SOFTWARE.
*/ */
#include <stdlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <vector>
#include <map>
typedef std::vector<gid_t> gid_t_vector;
typedef std::map<uid_t,gid_t_vector> gid_t_cache;
#include "gidcache.hpp"
#if defined __linux__ and UGID_USE_RWLOCK == 0 #if defined __linux__ and UGID_USE_RWLOCK == 0
#include "ugid_linux.ipp" #include "ugid_linux.ipp"
@ -44,43 +34,13 @@ namespace mergerfs
{ {
namespace ugid namespace ugid
{ {
static
inline
void
prime_cache(const uid_t uid,
const gid_t gid,
gid_t_vector &gidlist)
{
int rv;
char buf[4096];
struct passwd pwd;
struct passwd *pwdrv;
rv = getpwuid_r(uid,&pwd,buf,sizeof(buf),&pwdrv);
if(pwdrv != NULL && rv == 0)
{
int count;
count = 0;
rv = ::getgrouplist(pwd.pw_name,gid,NULL,&count);
gidlist.resize(count);
rv = ::getgrouplist(pwd.pw_name,gid,&gidlist[0],&count);
if(rv == -1)
gidlist.resize(1,gid);
}
}
void void
initgroups(const uid_t uid, initgroups(const uid_t uid,
const gid_t gid) const gid_t gid)
{ {
static __thread gid_t_cache cache;
gid_t_vector &gidlist = cache[uid];
if(gidlist.empty())
prime_cache(uid,gid,gidlist);
static __thread gid_t_cache cache = {0};
setgroups(gidlist);
cache.initgroups(uid,gid);
} }
} }
} }
Loading…
Cancel
Save