diff --git a/README.md b/README.md index 5928ee42..c78dca8b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ % mergerfs(1) mergerfs user manual % Antonio SJ Musumeci -% 2015-09-14 +% 2015-10-11 # NAME @@ -14,7 +14,7 @@ mergerfs -o<options> <srcpoints> <mountpoint> **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 @@ -288,7 +288,8 @@ A B C # 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 diff --git a/src/gidcache.cpp b/src/gidcache.cpp new file mode 100644 index 00000000..4a4cd765 --- /dev/null +++ b/src/gidcache.cpp @@ -0,0 +1,143 @@ +/* + The MIT License (MIT) + + Copyright (c) 2015 Antonio SJ Musumeci + + 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 +#include +#include +#include +#include + +#include +#include + +#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); + } +} diff --git a/src/gidcache.hpp b/src/gidcache.hpp new file mode 100644 index 00000000..29ba1c2b --- /dev/null +++ b/src/gidcache.hpp @@ -0,0 +1,66 @@ +/* + The MIT License (MIT) + + Copyright (c) 2015 Antonio SJ Musumeci + + 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 +#include + +#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 diff --git a/src/ugid.cpp b/src/ugid.cpp index 28cf5d93..c0ea9866 100644 --- a/src/ugid.cpp +++ b/src/ugid.cpp @@ -1,38 +1,28 @@ /* - The MIT License (MIT) - - Copyright (c) 2014 Antonio SJ Musumeci - - 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. + The MIT License (MIT) + + Copyright (c) 2015 Antonio SJ Musumeci + + 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 -#include -#include -#include -#include - -#include -#include - -typedef std::vector gid_t_vector; -typedef std::map gid_t_cache; +#include "gidcache.hpp" #if defined __linux__ and UGID_USE_RWLOCK == 0 #include "ugid_linux.ipp" @@ -44,43 +34,13 @@ namespace mergerfs { 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 initgroups(const uid_t uid, 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); } } }