IDmap support for the NFSv4 server. fs/nfsd/Makefile | 2 fs/nfsd/nfs4idmap.c | 490 +++++++++++++++++++++++++++++++++++++ fs/nfsd/nfsctl.c | 7 include/linux/nfsd_idmap.h | 46 +++ include/linux/sunrpc/name_lookup.h | 54 +++- 5 files changed, 587 insertions(+), 12 deletions(-) diff -puN fs/nfsd/Makefile~idmap_server fs/nfsd/Makefile --- linux-2.5.68/fs/nfsd/Makefile~idmap_server 2003-05-18 02:47:51.000000000 -0400 +++ linux-2.5.68-marius/fs/nfsd/Makefile 2003-05-18 02:47:51.000000000 -0400 @@ -7,5 +7,5 @@ obj-$(CONFIG_NFSD) += nfsd.o nfsd-y := nfssvc.o nfsctl.o nfsproc.o nfsfh.o vfs.o \ export.o auth.o lockd.o nfscache.o nfsxdr.o stats.o nfsd-$(CONFIG_NFSD_V3) += nfs3proc.o nfs3xdr.o -nfsd-$(CONFIG_NFSD_V4) += nfs4proc.o nfs4xdr.o nfs4state.o +nfsd-$(CONFIG_NFSD_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4idmap.o nfsd-objs := $(nfsd-y) diff -puN /dev/null fs/nfsd/nfs4idmap.c --- /dev/null 2002-12-13 17:52:05.000000000 -0500 +++ linux-2.5.68-marius/fs/nfsd/nfs4idmap.c 2003-05-18 02:47:52.000000000 -0400 @@ -0,0 +1,490 @@ +/* + * fs/nfsd/nfs4idmap.c + * + * Mapping of UID/GIDs to name and vice versa. + * + * Copyright (c) 2002, 2003 The Regents of the University of + * Michigan. All rights reserved. + * + * Marius Aamodt Eriksen + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Cache entry + */ + +#define ENT_INVALID 0x1 +struct ent { + struct cache_head h; + int type; /* User / Group */ + uid_t id; + char name[IDMAP_NAMESZ]; + long flags; +}; + +#define DefineSimpleCacheLookupMap(STRUCT, FUNC) \ + DefineCacheLookup(struct STRUCT, h, FUNC##_lookup, \ + (struct STRUCT *item, int set), /*no setup */, \ + & FUNC##_cache, FUNC##_hash(item), FUNC##_match(item, tmp), \ + STRUCT##_init(new, item), STRUCT##_update(tmp, item)) + +#define IDMAP_WAITANSWER 0x1 +struct idmap_defer_req { + struct cache_req req; + struct cache_deferred_req deferred_req; + wait_queue_head_t waitq; + unsigned long flags; +}; + +static size_t strlcpy(char *, const char *, size_t); +static int validate_ascii(char *, uint32_t); + +/* Deferral helpers */ +static struct cache_deferred_req *idmap_defer(struct cache_req *); +static void idmap_revisit(struct cache_deferred_req *, + int); +/* Common entry handling */ + +#define ENT_HASHBITS 8 +#define ENT_HASHMAX (1 << ENT_HASHBITS) +#define ENT_HASHMASK (ENT_HASHMAX - 1) + +static inline void +ent_init(struct ent *new, struct ent *itm) +{ + new->id = itm->id; + new->type = itm->type; + new->flags = itm->flags; + + strlcpy(new->name, itm->name, sizeof(new->name)); +} + +static inline void +ent_update(struct ent *new, struct ent *itm) +{ + ent_init(new, itm); +} + +void +ent_put(struct cache_head *ch, struct cache_detail *cd) +{ + if (cache_put(ch, cd)) { + struct ent *map = container_of(ch, struct ent, h); + kfree(map); + } +} + +/* + * ID -> Name cache + */ + +static struct cache_head *idtoname_table[ENT_HASHMAX]; + +static uint32_t +idtoname_hash(struct ent *ent) +{ + uint32_t hash; + + hash = ent->id; + + /* Flip LSB for user/group */ + if (ent->type == IDMAP_TYPE_GROUP) + hash |= 1 << (ENT_HASHBITS - 1); + else + hash &= ~(1 << (ENT_HASHBITS - 1)); + + return (hash & ENT_HASHMASK); +} + +static void +idtoname_request(struct cache_detail *cd, struct cache_head *ch, char **bpp, + int *blen) +{ + struct ent *ent = container_of(ch, struct ent, h); + struct idmap_msg *msg = (struct idmap_msg *)*bpp; + + if (*blen < sizeof(*msg)) + return; + + memset(msg, 0, sizeof(*msg)); + + msg->im_conv = IDMAP_TRANS_IDTONAME; + msg->im_type = ent->type; + msg->im_id = ent->id; + + *bpp += sizeof(*msg); + *blen -= sizeof(*msg); +} + +static inline int +idtoname_match(struct ent *a, struct ent *b) +{ + return (a->id == b->id && a->type == b->type); +} + +static int idtoname_parse(struct cache_detail *, char *, int); +static struct ent *idtoname_lookup(struct ent *, int); + +struct cache_detail idtoname_cache = { + .hash_size = ENT_HASHMAX, + .hash_table = idtoname_table, + .name = "nfs4.idtoname", + .cache_put = ent_put, + .cache_request = idtoname_request, + .cache_parse = idtoname_parse, +}; + +int +idtoname_parse(struct cache_detail *cd, char *buf, int buflen) +{ + struct ent ent, *res; + struct idmap_msg *msg = (struct idmap_msg *)buf; + + if (buflen != sizeof(*msg) || + validate_ascii(msg->im_name, IDMAP_NAMESZ) < 0 || msg->im_type > 1) + return (-EINVAL); + + memset(&ent, 0, sizeof(ent)); + + if (msg->im_status & IDMAP_STATUS_FAIL) + set_bit(ENT_INVALID, &ent.flags); + + ent.id = msg->im_id; + memcpy(ent.name, msg->im_name, sizeof(ent.name)); + + ent.type = msg->im_type; + + ent.h.flags = 0; + ent.h.expiry_time = CURRENT_TIME.tv_sec + 3600; + + if ((res = idtoname_lookup(&ent, 2)) == NULL) + return (-ENOMEM); + + ent_put(&res->h, &idtoname_cache); + + return (0); +} + +static DefineSimpleCacheLookupMap(ent, idtoname) + +/* + * Name -> ID cache + */ + +static struct cache_head *nametoid_table[ENT_HASHMAX]; + +/* + * Fowler/Noll/Vo hash + * http://www.isthe.com/chongo/tech/comp/fnv/ + * + * XXX - adopt this to a true ENT_HASHBITS-bit FNV + */ + +#define FNV_P_32 ((u_int32_t)0x01000193) /* 16777619 */ +#define FNV_1_32 ((u_int32_t)0x811c9dc5) /* 2166136261 */ + +static inline int +nametoid_hash(struct ent *ent) +{ + u_char *p, *end; + uint32_t hash = FNV_1_32; + + end = ent->name + strlen(ent->name); + + for (p = ent->name; p < end; p++) { + hash *= FNV_P_32; + hash ^= (u_int32_t)*p; + } + + return (hash & ENT_HASHMASK); +} + +void +nametoid_request(struct cache_detail *cd, struct cache_head *ch, char **bpp, + int *blen) +{ + struct ent *ent = container_of(ch, struct ent, h); + struct idmap_msg *msg = (struct idmap_msg *)*bpp; + + if (*blen < sizeof(*msg)) + return; + + memset(msg, 0, sizeof(*msg)); + + msg->im_conv = IDMAP_TRANS_NAMETOID; + msg->im_type = ent->type; + strlcpy(msg->im_name, ent->name, sizeof(msg->im_name)); + + *bpp += sizeof(*msg); + *blen -= sizeof(*msg); +} + +static inline int +nametoid_match(struct ent *a, struct ent *b) +{ + return (a->type == b->type && strcmp(a->name, b->name) == 0); +} + +static struct ent *nametoid_lookup(struct ent *, int); +int nametoid_parse(struct cache_detail *, char *, int); + +struct cache_detail nametoid_cache = { + .hash_size = ENT_HASHMAX, + .hash_table = nametoid_table, + .name = "nfs4.nametoid", + .cache_put = ent_put, + .cache_request = nametoid_request, + .cache_parse = nametoid_parse, +}; + +int +nametoid_parse(struct cache_detail *cd, char *buf, int buflen) +{ + struct ent ent, *res; + struct idmap_msg *msg = (struct idmap_msg *)buf; + + if (buflen != sizeof(*msg) || + validate_ascii(msg->im_name, IDMAP_NAMESZ) < 0 || msg->im_type > 1) + return (-EINVAL); + + memset(&ent, 0, sizeof(ent)); + + if (msg->im_status & IDMAP_STATUS_FAIL) + set_bit(ENT_INVALID, &ent.flags); + + ent.id = msg->im_id; + strlcpy(ent.name, msg->im_name, sizeof(ent.name)); + ent.type = msg->im_type; + + ent.h.flags = 0; + ent.h.expiry_time = CURRENT_TIME.tv_sec + 3600; + + if ((res = nametoid_lookup(&ent, 2)) == NULL) + return (-ENOMEM); + + ent_put(&res->h, &nametoid_cache); + + return (0); +} + +static DefineSimpleCacheLookupMap(ent, nametoid) + +/* + * Deferred request handling + */ + +static struct cache_deferred_req * +idmap_defer(struct cache_req *req) +{ + struct idmap_defer_req *mdr = + container_of(req, struct idmap_defer_req, req); + + mdr->deferred_req.revisit = idmap_revisit; + + return (&mdr->deferred_req); +} + +static void +idmap_revisit(struct cache_deferred_req *dreq, int toomany) +{ + struct idmap_defer_req *mdr = + container_of(dreq, struct idmap_defer_req, deferred_req); + + if (test_bit(IDMAP_WAITANSWER, &mdr->flags)) { + if (!toomany) + clear_bit(IDMAP_WAITANSWER, &mdr->flags); + + wake_up(&mdr->waitq); + } +} + +/* + * Exported API + */ + +void +nfsd_idmap_init(void) +{ + cache_register(&idtoname_cache); + cache_register(&nametoid_cache); +} + +void +nfsd_idmap_shutdown(void) +{ + cache_unregister(&idtoname_cache); + cache_unregister(&nametoid_cache); +} + +int +nfsd_idmap(short trans, short type, uid_t *id, char *name) +{ + struct ent key, *res; + struct idmap_defer_req mdr; + DECLARE_WAITQUEUE(waitq, current); + struct ent *(*lookup_fn)(struct ent *, int); + struct cache_detail *lookup_cache; + int valid; + + if (name == NULL || id == NULL) + return (-1); + + memset(&key, 0, sizeof(key)); + + switch (trans) { + case IDMAP_TRANS_IDTONAME: + lookup_fn = idtoname_lookup; + lookup_cache = &idtoname_cache; + key.id = *id; + break; + case IDMAP_TRANS_NAMETOID: + lookup_fn = nametoid_lookup; + lookup_cache = &nametoid_cache; + strlcpy(key.name, name, sizeof(key.name)); + break; + default: + return (-1); + } + + key.type = type; + + memset(&mdr, 0, sizeof(mdr)); + init_waitqueue_head(&mdr.waitq); + mdr.req.defer = idmap_defer; + + lookup: + if ((res = (*lookup_fn)(&key, 0)) != NULL) { + add_wait_queue(&mdr.waitq, &waitq); + again: + set_task_state(current, TASK_UNINTERRUPTIBLE); + set_bit(IDMAP_WAITANSWER, &mdr.flags); + if (cache_check(lookup_cache, &res->h, &mdr.req) != 0) { + schedule(); + + /* + * If we haven't gotten the answer yet; we + * were woken up due to an overpopulated + * cache. Try again! + */ + + if (test_bit(IDMAP_WAITANSWER, &mdr.flags)) + goto again; + + goto lookup; + } + + *id = res->id; + strlcpy(name, res->name, IDMAP_NAMESZ); + valid = !test_bit(ENT_INVALID, &res->flags); + ent_put(&res->h, lookup_cache); + + set_task_state(current, TASK_RUNNING); + + return (valid ? 0 : -1); + } + + return (-1); +} + +/* Utility */ + +/* strlcpy() from OpenBSD. */ + +/* + * Copy src to string dst of size siz. At most siz-1 characters will + * be copied. Always NUL terminates (unless siz == 0). Returns + * strlen(src); if retval >= siz, truncation occurred. + */ +static size_t +strlcpy(dst, src, siz) + char *dst; + const char *src; + size_t siz; +{ + register char *d = dst; + register const char *s = src; + register size_t n = siz; + + /* Copy as many bytes as will fit */ + if (n != 0 && --n != 0) { + do { + if ((*d++ = *s++) == 0) + break; + } while (--n != 0); + } + + /* Not enough room in dst, add NUL and traverse rest of src */ + if (n == 0) { + if (siz != 0) + *d = '\0'; /* NUL-terminate dst */ + while (*s++) + ; + } + + return(s - src - 1); /* count does not include NUL */ +} + +static int +validate_ascii(char *string, uint32_t len) +{ + int i; + + for (i = 0; i < len; i++) { + if (string[i] == '\0') + break; + + if (string[i] & 0x80) + return (-1); + } + + if (string[i] != '\0') + return (-i); + + return (i); +} diff -puN fs/nfsd/nfsctl.c~idmap_server fs/nfsd/nfsctl.c --- linux-2.5.68/fs/nfsd/nfsctl.c~idmap_server 2003-05-18 02:47:51.000000000 -0400 +++ linux-2.5.68-marius/fs/nfsd/nfsctl.c 2003-05-18 02:47:52.000000000 -0400 @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -513,6 +514,9 @@ static int __init init_nfsd(void) nfsd_export_init(); /* Exports table */ nfsd_lockd_init(); /* lockd->nfsd callbacks */ nfs4_state_init(); /* NFSv4 State */ +#ifdef CONFIG_NFSD_V4 + nfsd_idmap_init(); /* Name to ID mapping */ +#endif /* CONFIG_NFSD_V4 */ if (proc_mkdir("fs/nfs", 0)) { struct proc_dir_entry *entry; entry = create_proc_entry("fs/nfs/exports", 0, NULL); @@ -531,6 +535,9 @@ static void __exit exit_nfsd(void) remove_proc_entry("fs/nfs", NULL); nfsd_stat_shutdown(); nfsd_lockd_shutdown(); +#ifdef CONFIG_NFSD_V4 + nfsd_idmap_shutdown(); +#endif /* CONFIG_NFSD_V4 */ nfs4_state_shutdown(); unregister_filesystem(&nfsd_fs_type); } diff -puN /dev/null include/linux/nfsd_idmap.h --- /dev/null 2002-12-13 17:52:05.000000000 -0500 +++ linux-2.5.68-marius/include/linux/nfsd_idmap.h 2003-05-18 02:47:52.000000000 -0400 @@ -0,0 +1,46 @@ +/* + * include/linux/nfsd_idmap.h + * + * Mapping of UID to name and vice versa. + * + * Copyright (c) 2002, 2003 The Regents of the University of + * Michigan. All rights reserved. + * + * Marius Aamodt Eriksen + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LINUX_NFSD_IDMAP_H +#define LINUX_NFSD_IDMAP_H + +#include + +void nfsd_idmap_init(void); +void nfsd_idmap_shutdown(void); +int nfsd_idmap(short, short, uid_t *, char *); + +#endif /* LINUX_NFSD_IDMAP_H */ diff -puN include/linux/sunrpc/name_lookup.h~idmap_server include/linux/sunrpc/name_lookup.h --- linux-2.5.68/include/linux/sunrpc/name_lookup.h~idmap_server 2003-05-18 02:47:51.000000000 -0400 +++ linux-2.5.68-marius/include/linux/sunrpc/name_lookup.h 2003-05-18 02:47:52.000000000 -0400 @@ -3,33 +3,65 @@ * map between user/group name and id for a given 'client' */ +#include +#include + struct name_ent { - char name[20]; + char name[IDMAP_NAMESZ]; }; static inline int name_get_user(int uid, struct name_ent **namep) { - struct name_ent *n = kmalloc(sizeof(*n),GFP_KERNEL); - if (n) sprintf(n->name, "%d",uid); + struct name_ent *n = kmalloc(sizeof(*n), GFP_KERNEL); + + if (n == NULL) + return (-ENOMEM); + *namep = n; - return n ? 0 : -ENOMEM; + return (nfsd_idmap(IDMAP_TRANS_IDTONAME, IDMAP_TYPE_USER, &uid, n->name)); } static inline int name_get_group(int uid, struct name_ent **namep) { - struct name_ent *n = kmalloc(sizeof(*n),GFP_KERNEL); - if (n) sprintf(n->name, "%d",uid); + struct name_ent *n = kmalloc(sizeof(*n), GFP_KERNEL); + + if (n == NULL) + return (-ENOMEM); + *namep = n; - return n ? 0 : -ENOMEM; + return (nfsd_idmap(IDMAP_TRANS_IDTONAME, IDMAP_TYPE_GROUP, &uid, n->name)); + } static inline int name_get_uid(char *name, int name_len, int *uidp) { - *uidp = simple_strtoul(name, NULL, 0); - return 0; + char *xname = kmalloc(name_len + 1, GFP_KERNEL); + int ret; + + if (xname == NULL) + return (-ENOMEM); + + memcpy(xname, name, name_len); + xname[name_len] = '\0'; + + ret = nfsd_idmap(IDMAP_TRANS_NAMETOID, IDMAP_TYPE_USER, uidp, xname); + + kfree(xname); + return (ret); } static inline int name_get_gid(char *name, int name_len, int *gidp) { - *gidp = simple_strtoul(name, NULL, 0); - return 0; + char *xname = kmalloc(name_len + 1, GFP_KERNEL); + int ret; + + if (xname == NULL) + return (-ENOMEM); + + memcpy(xname, name, name_len); + xname[name_len] = '\0'; + + ret = nfsd_idmap(IDMAP_TRANS_NAMETOID, IDMAP_TYPE_GROUP, gidp, xname); + + kfree(xname); + return (ret); } static inline void name_put(struct name_ent *ent) _