diff options
Diffstat (limited to 'xlators/nfs/server/src/nfs-fops.c')
| -rw-r--r-- | xlators/nfs/server/src/nfs-fops.c | 159 | 
1 files changed, 151 insertions, 8 deletions
diff --git a/xlators/nfs/server/src/nfs-fops.c b/xlators/nfs/server/src/nfs-fops.c index 6e2b33484..87c511d54 100644 --- a/xlators/nfs/server/src/nfs-fops.c +++ b/xlators/nfs/server/src/nfs-fops.c @@ -22,6 +22,9 @@  #include "config.h"  #endif +#include <grp.h> +#include <pwd.h> +  #include "dict.h"  #include "xlator.h"  #include "iobuf.h" @@ -32,9 +35,143 @@  #include "inode.h"  #include "nfs-common.h"  #include "nfs3-helpers.h" +#include "nfs-mem-types.h"  #include <libgen.h>  #include <semaphore.h> +/* + * We treat this as a very simple set-associative LRU cache, with entries aged + * out after a configurable interval.  Hardly rocket science, but lots of + * details to worry about. + */ +#define BUCKET_START(p,n)       ((p) + ((n) * AUX_GID_CACHE_ASSOC)) + +void +nfs_fix_groups (xlator_t *this, call_stack_t *root) +{ +        struct passwd    mypw; +        char             mystrs[1024]; +        struct passwd    *result; +        gid_t            mygroups[GF_MAX_AUX_GROUPS]; +        int              ngroups; +        int              i; +        struct nfs_state *priv = this->private; +        aux_gid_list_t   *agl = NULL; +        int              bucket = 0; +        time_t           now = 0; + +        if (!priv->server_aux_gids) { +                return; +        } + +        LOCK(&priv->aux_gid_lock); +        now = time(NULL); +        bucket = root->uid % priv->aux_gid_nbuckets; +        agl = BUCKET_START(priv->aux_gid_cache,bucket); +        for (i = 0; i < AUX_GID_CACHE_ASSOC; ++i, ++agl) { +                if (!agl->gid_list) { +                        continue; +                } +                if (agl->uid != root->uid) { +                        continue; +                } +                /* +                 * We don't put new entries in the cache when expiration=0, but +                 * there might be entries still in there if expiration was +                 * changed very recently.  Writing the check this way ensures +                 * that they're not used. +                 */ +                if (now < agl->deadline) { +                        for (ngroups = 0; ngroups < agl->gid_count; ++ngroups) { +                                root->groups[ngroups] = agl->gid_list[ngroups]; +                        } +                        UNLOCK(&priv->aux_gid_lock); +                        root->ngrps = ngroups; +                        return; +                } +                /* +                 * We're not going to find any more UID matches, and reaping +                 * is handled further down to maintain LRU order. +                 */ +                break; +        } +        UNLOCK(&priv->aux_gid_lock); + +        if (getpwuid_r(root->uid,&mypw,mystrs,sizeof(mystrs),&result) != 0) { +                gf_log (this->name, GF_LOG_ERROR, +                        "getpwuid_r(%u) failed", root->uid); +                return; +        } + +        if (!result) { +                gf_log (this->name, GF_LOG_ERROR, +                        "getpwuid_r(%u) found nothing", root->uid); +                return; +        } + +        gf_log (this->name, GF_LOG_TRACE, "mapped %u => %s", +                root->uid, result->pw_name); + +        ngroups = GF_MAX_AUX_GROUPS; +        if (getgrouplist(result->pw_name,root->gid,mygroups,&ngroups) == -1) { +                gf_log (this->name, GF_LOG_ERROR, +                        "could not map %s to group list", result->pw_name); +                return; +        } + +        if (priv->aux_gid_max_age) { +                LOCK(&priv->aux_gid_lock); +                /* Bucket should still be valid from before. */ +                agl = BUCKET_START(priv->aux_gid_cache,bucket); +                for (i = 0; i < AUX_GID_CACHE_ASSOC; ++i, ++agl) { +                        if (!agl->gid_list) { +                                break; +                        } +                } +                /* +                 * The way we allocate free entries naturally places the newest +                 * ones at the highest indices, so evicting the lowest makes +                 * sense, but that also means we can't just replace it with the +                 * one that caused the eviction.  That would cause us to thrash +                 * the first entry while others remain idle.  Therefore, we +                 * need to slide the other entries down and add the new one at +                 * the end just as if the *last* slot had been free. +                 * +                 * Deadline expiration is also handled here, since the oldest +                 * expired entry will be in the first position.  This does mean +                 * the bucket can stay full of expired entries if we're idle +                 * but, if the small amount of extra memory or scan time before +                 * we decide to evict someone ever become issues, we could +                 * easily add a reaper thread. +                 */ +                if (i >= AUX_GID_CACHE_ASSOC) { +                        agl = BUCKET_START(priv->aux_gid_cache,bucket); +                        GF_FREE(agl->gid_list); +                        for (i = 1; i < AUX_GID_CACHE_ASSOC; ++i) { +                                agl[0] = agl[1]; +                                ++agl; +                        } +                } +                agl->gid_list = GF_CALLOC(ngroups,sizeof(gid_t), +                                          gf_nfs_mt_aux_gids); +                if (agl->gid_list) { +                        /* It's not fatal if the alloc failed. */ +                        agl->uid = root->uid; +                        agl->gid_count = ngroups; +                        memcpy(agl->gid_list,mygroups,sizeof(gid_t)*ngroups); +                        agl->deadline = now + priv->aux_gid_max_age; +                } +                UNLOCK(&priv->aux_gid_lock); +        } + +        for (i = 0; i < ngroups; ++i) { +                gf_log (this->name, GF_LOG_TRACE, +                        "%s is in group %u", result->pw_name, mygroups[i]); +                root->groups[i] = mygroups[i]; +        } +        root->ngrps = ngroups; +} +  struct nfs_fop_local *  nfs_fop_local_init (xlator_t *nfsx)  { @@ -122,18 +259,24 @@ nfs_create_frame (xlator_t *xl, nfs_user_t *nfu)          frame->root->uid = nfu->uid;          frame->root->gid = nfu->gids[NFS_PRIMGID_IDX];          frame->root->lk_owner = nfu->lk_owner; -        if (nfu->ngrps == 1) -                goto err;       /* Done, we only got primary gid */ -        frame->root->ngrps = nfu->ngrps - 1; +        if (nfu->ngrps != 1) { +                frame->root->ngrps = nfu->ngrps - 1; -        gf_log (GF_NFS, GF_LOG_TRACE,"uid: %d, gid %d, gids: %d", -                frame->root->uid, frame->root->gid, frame->root->ngrps); -        for(y = 0, x = 1;  y < frame->root->ngrps; x++,y++) { -                gf_log (GF_NFS, GF_LOG_TRACE, "gid: %d", nfu->gids[x]); -                frame->root->groups[y] = nfu->gids[x]; +                gf_log (GF_NFS, GF_LOG_TRACE,"uid: %d, gid %d, gids: %d", +                        frame->root->uid, frame->root->gid, frame->root->ngrps); +                for(y = 0, x = 1;  y < frame->root->ngrps; x++,y++) { +                        gf_log (GF_NFS, GF_LOG_TRACE, "gid: %d", nfu->gids[x]); +                        frame->root->groups[y] = nfu->gids[x]; +                }          } +        /* +         * It's tempting to do this *instead* of using nfu above, but we need +         * to have those values in case nfs_fix_groups doesn't do anything. +         */ +        nfs_fix_groups(xl,frame->root); +  err:          return frame;  }  | 
