Commit b26ef9fe authored by Trond Myklebust's avatar Trond Myklebust

NFSv4: Bugfixes and cleanups for the NFSv4 client

name to uid mapper. Includes a fix by Tim Woods to
deal with a caching bug in the case where a user
and a group share the same numerical id and/or name.
parent 066a0e3d
......@@ -52,14 +52,16 @@
#include <linux/nfs_idmap.h>
#define IDMAP_HASH_SZ 128
#define IDMAP_HASH_TYPE_NAME 0x01
#define IDMAP_HASH_TYPE_ID 0x02
#define IDMAP_HASH_TYPE_INSERT 0x04
struct idmap_hashent {
uid_t ih_id;
char ih_name[IDMAP_NAMESZ];
u_int32_t ih_namelen;
__u32 ih_id;
int ih_namelen;
char ih_name[IDMAP_NAMESZ];
};
struct idmap_hashtable {
__u8 h_type;
struct idmap_hashent h_entries[IDMAP_HASH_SZ];
};
struct idmap {
......@@ -67,12 +69,10 @@ struct idmap {
struct dentry *idmap_dentry;
wait_queue_head_t idmap_wq;
struct idmap_msg idmap_im;
struct nfs_server *idmap_server;
struct semaphore idmap_lock;
struct semaphore idmap_im_lock;
struct semaphore idmap_hash_lock;
struct idmap_hashent idmap_id_hash[IDMAP_HASH_SZ];
struct idmap_hashent idmap_name_hash[IDMAP_HASH_SZ];
struct semaphore idmap_lock; /* Serializes upcalls */
struct semaphore idmap_im_lock; /* Protects the hashtable */
struct idmap_hashtable idmap_user_hash;
struct idmap_hashtable idmap_group_hash;
};
static ssize_t idmap_pipe_upcall(struct file *, struct rpc_pipe_msg *, char *,
......@@ -80,10 +80,7 @@ static ssize_t idmap_pipe_upcall(struct file *, struct rpc_pipe_msg *, char *,
static ssize_t idmap_pipe_downcall(struct file *, const char *, size_t);
void idmap_pipe_destroy_msg(struct rpc_pipe_msg *);
static int validate_ascii(char *, u_int32_t);
static u_int32_t fnvhash32(void *, u_int32_t);
static int idmap_cache_lookup(struct idmap *, int, char *, u_int32_t *, uid_t *);
static unsigned int fnvhash32(const void *, size_t);
static struct rpc_pipe_ops idmap_upcall_ops = {
.upcall = idmap_pipe_upcall,
......@@ -101,20 +98,19 @@ nfs_idmap_new(struct nfs_server *server)
memset(idmap, 0, sizeof(*idmap));
idmap->idmap_server = server;
snprintf(idmap->idmap_path, sizeof(idmap->idmap_path),
"%s/idmap", idmap->idmap_server->client->cl_pathname);
"%s/idmap", server->client->cl_pathname);
idmap->idmap_dentry = rpc_mkpipe(idmap->idmap_path,
idmap->idmap_server, &idmap_upcall_ops, 0);
idmap, &idmap_upcall_ops, 0);
if (IS_ERR(idmap->idmap_dentry))
goto err_free;
init_MUTEX(&idmap->idmap_lock);
init_MUTEX(&idmap->idmap_im_lock);
init_MUTEX(&idmap->idmap_hash_lock);
init_waitqueue_head(&idmap->idmap_wq);
idmap->idmap_user_hash.h_type = IDMAP_TYPE_USER;
idmap->idmap_group_hash.h_type = IDMAP_TYPE_GROUP;
return (idmap);
......@@ -135,35 +131,102 @@ nfs_idmap_delete(struct nfs_server *server)
kfree(idmap);
}
/*
* Helper routines for manipulating the hashtable
*/
static inline struct idmap_hashent *
idmap_name_hash(struct idmap_hashtable* h, const char *name, size_t len)
{
return &h->h_entries[fnvhash32(name, len) % IDMAP_HASH_SZ];
}
static struct idmap_hashent *
idmap_lookup_name(struct idmap_hashtable *h, const char *name, size_t len)
{
struct idmap_hashent *he = idmap_name_hash(h, name, len);
if (he->ih_namelen != len || memcmp(he->ih_name, name, len) != 0)
return NULL;
return he;
}
static inline struct idmap_hashent *
idmap_id_hash(struct idmap_hashtable* h, __u32 id)
{
return &h->h_entries[fnvhash32(&id, sizeof(id)) % IDMAP_HASH_SZ];
}
static struct idmap_hashent *
idmap_lookup_id(struct idmap_hashtable *h, __u32 id)
{
struct idmap_hashent *he = idmap_id_hash(h, id);
if (he->ih_id != id || he->ih_namelen == 0)
return NULL;
return he;
}
/*
* Routines for allocating new entries in the hashtable.
* For now, we just have 1 entry per bucket, so it's all
* pretty trivial.
*/
static inline struct idmap_hashent *
idmap_alloc_name(struct idmap_hashtable *h, char *name, unsigned len)
{
return idmap_name_hash(h, name, len);
}
static inline struct idmap_hashent *
idmap_alloc_id(struct idmap_hashtable *h, __u32 id)
{
return idmap_id_hash(h, id);
}
static void
idmap_update_entry(struct idmap_hashent *he, const char *name,
size_t namelen, __u32 id)
{
he->ih_id = id;
memcpy(he->ih_name, name, namelen);
he->ih_name[namelen] = '\0';
he->ih_namelen = namelen;
}
/*
* Name -> ID
*/
int
nfs_idmap_id(struct nfs_server *server, u_int8_t type, char *name,
u_int namelen, uid_t *id)
static int
nfs_idmap_id(struct idmap *idmap, struct idmap_hashtable *h,
const char *name, size_t namelen, __u32 *id)
{
struct rpc_pipe_msg msg;
struct idmap *idmap = server->idmap;
struct idmap_msg *im;
struct idmap_hashent *he;
DECLARE_WAITQUEUE(wq, current);
int ret = -1, hashtype = IDMAP_HASH_TYPE_NAME;
u_int xnamelen = namelen;
if (idmap == NULL)
return (-1);
int ret = -EIO;
im = &idmap->idmap_im;
if (namelen > IDMAP_NAMESZ || namelen == 0)
return (-1);
/*
* String sanity checks
* Note that the userland daemon expects NUL terminated strings
*/
for (;;) {
if (namelen == 0)
return -EINVAL;
if (name[namelen-1] != '\0')
break;
namelen--;
}
if (namelen >= IDMAP_NAMESZ)
return -EINVAL;
down(&idmap->idmap_lock);
down(&idmap->idmap_im_lock);
if (name[xnamelen - 1] == '\0')
xnamelen--;
if (idmap_cache_lookup(idmap, hashtype, name, &xnamelen, id) == 0) {
he = idmap_lookup_name(h, name, namelen);
if (he != NULL) {
*id = he->ih_id;
ret = 0;
goto out;
}
......@@ -171,7 +234,7 @@ nfs_idmap_id(struct nfs_server *server, u_int8_t type, char *name,
memset(im, 0, sizeof(*im));
memcpy(im->im_name, name, namelen);
im->im_type = type;
im->im_type = h->h_type;
im->im_conv = IDMAP_CONV_NAMETOID;
memset(&msg, 0, sizeof(msg));
......@@ -191,16 +254,9 @@ nfs_idmap_id(struct nfs_server *server, u_int8_t type, char *name,
remove_wait_queue(&idmap->idmap_wq, &wq);
down(&idmap->idmap_im_lock);
/*
* XXX Race condition here, with testing for status. Go ahead
* and and do the cace lookup anyway.
*/
if (im->im_status & IDMAP_STATUS_SUCCESS) {
ret = 0;
*id = im->im_id;
hashtype |= IDMAP_HASH_TYPE_INSERT;
ret = idmap_cache_lookup(idmap, hashtype, name, &xnamelen, id);
ret = 0;
}
out:
......@@ -213,35 +269,31 @@ nfs_idmap_id(struct nfs_server *server, u_int8_t type, char *name,
/*
* ID -> Name
*/
int
nfs_idmap_name(struct nfs_server *server, u_int8_t type, uid_t id,
char *name, u_int *namelen)
static int
nfs_idmap_name(struct idmap *idmap, struct idmap_hashtable *h,
__u32 id, char *name)
{
struct rpc_pipe_msg msg;
struct idmap *idmap = server->idmap;
struct idmap_msg *im;
struct idmap_hashent *he;
DECLARE_WAITQUEUE(wq, current);
int ret = -1, hashtype = IDMAP_HASH_TYPE_ID;
u_int len;
if (idmap == NULL)
return (-1);
int ret = -EIO;
unsigned int len;
im = &idmap->idmap_im;
if (*namelen < IDMAP_NAMESZ || *namelen == 0)
return (-1);
down(&idmap->idmap_lock);
down(&idmap->idmap_im_lock);
if (idmap_cache_lookup(idmap, hashtype, name, namelen, &id) == 0) {
ret = 0;
he = idmap_lookup_id(h, id);
if (he != 0) {
memcpy(name, he->ih_name, he->ih_namelen);
ret = he->ih_namelen;
goto out;
}
memset(im, 0, sizeof(*im));
im->im_type = type;
im->im_type = h->h_type;
im->im_conv = IDMAP_CONV_IDTONAME;
im->im_id = id;
......@@ -256,9 +308,6 @@ nfs_idmap_name(struct nfs_server *server, u_int8_t type, uid_t id,
goto out;
}
/*
* XXX add timeouts here
*/
set_current_state(TASK_UNINTERRUPTIBLE);
up(&idmap->idmap_im_lock);
schedule();
......@@ -267,23 +316,20 @@ nfs_idmap_name(struct nfs_server *server, u_int8_t type, uid_t id,
down(&idmap->idmap_im_lock);
if (im->im_status & IDMAP_STATUS_SUCCESS) {
if ((len = validate_ascii(im->im_name, IDMAP_NAMESZ)) == -1)
if ((len = strnlen(im->im_name, IDMAP_NAMESZ)) == 0)
goto out;
ret = 0;
memcpy(name, im->im_name, len);
*namelen = len;
hashtype |= IDMAP_HASH_TYPE_INSERT;
ret = idmap_cache_lookup(idmap, hashtype, name, namelen, &id);
ret = len;
}
out:
memset(im, 0, sizeof(*im));
up(&idmap->idmap_im_lock);
up(&idmap->idmap_lock);
return (ret);
return ret;
}
/* RPC pipefs upcall/downcall routines */
static ssize_t
idmap_pipe_upcall(struct file *filp, struct rpc_pipe_msg *msg,
char *dst, size_t buflen)
......@@ -310,10 +356,12 @@ static ssize_t
idmap_pipe_downcall(struct file *filp, const char *src, size_t mlen)
{
struct rpc_inode *rpci = RPC_I(filp->f_dentry->d_inode);
struct nfs_server *server = rpci->private;
struct idmap *idmap = server->idmap;
struct idmap *idmap = (struct idmap *)rpci->private;
struct idmap_msg im_in, *im = &idmap->idmap_im;
int match = 0, hashtype, badmsg = 0, namelen_in, namelen;
struct idmap_hashtable *h;
struct idmap_hashent *he = NULL;
int namelen_in;
int ret;
if (mlen != sizeof(im_in))
return (-ENOSPC);
......@@ -323,39 +371,66 @@ idmap_pipe_downcall(struct file *filp, const char *src, size_t mlen)
down(&idmap->idmap_im_lock);
namelen_in = validate_ascii(im_in.im_name, IDMAP_NAMESZ);
namelen = validate_ascii(im->im_name, IDMAP_NAMESZ);
ret = mlen;
im->im_status = im_in.im_status;
/* If we got an error, terminate now, and wake up pending upcalls */
if (!(im_in.im_status & IDMAP_STATUS_SUCCESS)) {
wake_up(&idmap->idmap_wq);
goto out;
}
/* Sanity checking of strings */
ret = -EINVAL;
namelen_in = strnlen(im_in.im_name, IDMAP_NAMESZ);
if (namelen_in == 0 || namelen_in == IDMAP_NAMESZ)
goto out;
badmsg = !(im_in.im_status & IDMAP_STATUS_SUCCESS) || namelen_in <= 0;
switch (im_in.im_type) {
case IDMAP_TYPE_USER:
h = &idmap->idmap_user_hash;
break;
case IDMAP_TYPE_GROUP:
h = &idmap->idmap_group_hash;
break;
default:
goto out;
}
switch (im_in.im_conv) {
case IDMAP_CONV_IDTONAME:
match = im->im_id == im_in.im_id;
/* Did we match the current upcall? */
if (im->im_conv == IDMAP_CONV_IDTONAME
&& im->im_type == im_in.im_type
&& im->im_id == im_in.im_id) {
/* Yes: copy string, including the terminating '\0' */
memcpy(im->im_name, im_in.im_name, namelen_in);
im->im_name[namelen_in] = '\0';
wake_up(&idmap->idmap_wq);
}
he = idmap_alloc_id(h, im_in.im_id);
break;
case IDMAP_CONV_NAMETOID:
match = namelen == namelen_in &&
memcmp(im->im_name, im_in.im_name, namelen) == 0;
/* Did we match the current upcall? */
if (im->im_conv == IDMAP_CONV_NAMETOID
&& im->im_type == im_in.im_type
&& strnlen(im->im_name, IDMAP_NAMESZ) == namelen_in
&& memcmp(im->im_name, im_in.im_name, namelen_in) == 0) {
im->im_id = im_in.im_id;
wake_up(&idmap->idmap_wq);
}
he = idmap_alloc_name(h, im_in.im_name, namelen_in);
break;
default:
badmsg = 1;
break;
}
match = match && im->im_type == im_in.im_type;
if (match) {
memcpy(im, &im_in, sizeof(*im));
wake_up(&idmap->idmap_wq);
} else if (!badmsg) {
hashtype = im_in.im_conv == IDMAP_CONV_IDTONAME ?
IDMAP_HASH_TYPE_ID : IDMAP_HASH_TYPE_NAME;
hashtype |= IDMAP_HASH_TYPE_INSERT;
idmap_cache_lookup(idmap, hashtype, im_in.im_name, &namelen_in,
&im_in.im_id);
goto out;
}
/* If the entry is valid, also copy it to the cache */
if (he != NULL)
idmap_update_entry(he, im_in.im_name, namelen_in, im_in.im_id);
ret = mlen;
out:
up(&idmap->idmap_im_lock);
return (mlen);
return ret;
}
void
......@@ -372,108 +447,51 @@ idmap_pipe_destroy_msg(struct rpc_pipe_msg *msg)
up(&idmap->idmap_im_lock);
}
static int
validate_ascii(char *string, u_int32_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 (-1);
return (i);
}
/*
* Fowler/Noll/Vo hash
* http://www.isthe.com/chongo/tech/comp/fnv/
*/
#define FNV_P_32 ((u_int32_t)0x01000193) /* 16777619 */
#define FNV_1_32 ((u_int32_t)0x811c9dc5) /* 2166136261 */
#define FNV_P_32 ((unsigned int)0x01000193) /* 16777619 */
#define FNV_1_32 ((unsigned int)0x811c9dc5) /* 2166136261 */
static u_int32_t
fnvhash32(void *buf, u_int32_t buflen)
static unsigned int fnvhash32(const void *buf, size_t buflen)
{
u_char *p, *end = (u_char *)buf + buflen;
u_int32_t hash = FNV_1_32;
const unsigned char *p, *end = (const unsigned char *)buf + buflen;
unsigned int hash = FNV_1_32;
for (p = buf; p < end; p++) {
hash *= FNV_P_32;
hash ^= (u_int32_t)*p;
hash ^= (unsigned int)*p;
}
return (hash);
}
/*
* ->ih_namelen == 0 indicates negative entry
*/
static int
idmap_cache_lookup(struct idmap *idmap, int type, char *name, u_int32_t *namelen,
uid_t *id)
int nfs_map_name_to_uid(struct nfs_server *server, const char *name, size_t namelen, __u32 *uid)
{
u_int32_t hash;
struct idmap_hashent *he = NULL;
int insert = type & IDMAP_HASH_TYPE_INSERT;
int ret = -1;
/*
* XXX technically, this is not needed, since we will always
* hold idmap_im_lock when altering the hash tables. but
* semantically that just hurts.
*
* XXX cache negative responses
*/
down(&idmap->idmap_hash_lock);
struct idmap *idmap = server->idmap;
if (*namelen > IDMAP_NAMESZ || *namelen == 0)
goto out;
return nfs_idmap_id(idmap, &idmap->idmap_user_hash, name, namelen, uid);
}
if (type & IDMAP_HASH_TYPE_NAME) {
hash = fnvhash32(name, *namelen) % IDMAP_HASH_SZ;
he = &idmap->idmap_name_hash[hash];
/*
* Testing he->ih_namelen == *namelen implicitly tests
* namelen != 0, and thus a non-negative entry.
*/
if (!insert && he->ih_namelen == *namelen &&
memcmp(he->ih_name, name, *namelen) == 0) {
*id = he->ih_id;
ret = 0;
goto out;
}
}
int nfs_map_group_to_gid(struct nfs_server *server, const char *name, size_t namelen, __u32 *uid)
{
struct idmap *idmap = server->idmap;
if (type & IDMAP_HASH_TYPE_ID) {
hash = fnvhash32(id, sizeof(*id)) % IDMAP_HASH_SZ;
he = &idmap->idmap_id_hash[hash];
return nfs_idmap_id(idmap, &idmap->idmap_group_hash, name, namelen, uid);
}
if (!insert && *id == he->ih_id && he->ih_namelen != 0 &&
*namelen >= he->ih_namelen) {
memcpy(name, he->ih_name, he->ih_namelen);
*namelen = he->ih_namelen;
ret = 0;
goto out;
}
}
int nfs_map_uid_to_name(struct nfs_server *server, __u32 uid, char *buf)
{
struct idmap *idmap = server->idmap;
if (insert && he != NULL) {
he->ih_id = *id;
memcpy(he->ih_name, name, *namelen);
he->ih_namelen = *namelen;
ret = 0;
}
return nfs_idmap_name(idmap, &idmap->idmap_user_hash, uid, buf);
}
int nfs_map_gid_to_group(struct nfs_server *server, __u32 uid, char *buf)
{
struct idmap *idmap = server->idmap;
out:
up(&idmap->idmap_hash_lock);
return (ret);
return nfs_idmap_name(idmap, &idmap->idmap_group_hash, uid, buf);
}
......@@ -239,10 +239,10 @@ static int
encode_attrs(struct xdr_stream *xdr, struct iattr *iap,
struct nfs_server *server)
{
char owner_name[256];
char owner_group[256];
int owner_namelen = sizeof(owner_name);
int owner_grouplen = sizeof(owner_group);
char owner_name[IDMAP_NAMESZ];
char owner_group[IDMAP_NAMESZ];
int owner_namelen = 0;
int owner_grouplen = 0;
uint32_t *p;
uint32_t *q;
int len;
......@@ -265,9 +265,8 @@ encode_attrs(struct xdr_stream *xdr, struct iattr *iap,
if (iap->ia_valid & ATTR_MODE)
len += 4;
if (iap->ia_valid & ATTR_UID) {
status = nfs_idmap_name(server, IDMAP_TYPE_USER,
iap->ia_uid, owner_name, &owner_namelen);
if (status < 0) {
owner_namelen = nfs_map_uid_to_name(server, iap->ia_uid, owner_name);
if (owner_namelen < 0) {
printk(KERN_WARNING "nfs: couldn't resolve uid %d to string\n",
iap->ia_uid);
/* XXX */
......@@ -278,9 +277,8 @@ encode_attrs(struct xdr_stream *xdr, struct iattr *iap,
len += 4 + (XDR_QUADLEN(owner_namelen) << 2);
}
if (iap->ia_valid & ATTR_GID) {
status = nfs_idmap_name(server, IDMAP_TYPE_GROUP,
iap->ia_gid, owner_group, &owner_grouplen);
if (status < 0) {
owner_grouplen = nfs_map_gid_to_group(server, iap->ia_gid, owner_group);
if (owner_grouplen < 0) {
printk(KERN_WARNING "nfs4: couldn't resolve gid %d to string\n",
iap->ia_gid);
strcpy(owner_group, "nobody");
......@@ -1475,10 +1473,9 @@ decode_getattr(struct xdr_stream *xdr, struct nfs4_getattr *getattr,
}
READ_BUF(dummy32);
len += (XDR_QUADLEN(dummy32) << 2);
if ((status = nfs_idmap_id(server, IDMAP_TYPE_USER,
(char *)p, dummy32, &nfp->uid)) == -1) {
dprintk("read_attrs: gss_get_num failed!\n");
/* goto out; */
if ((status = nfs_map_name_to_uid(server, (char *)p, dummy32,
&nfp->uid)) < 0) {
dprintk("read_attrs: name-to-uid mapping failed!\n");
nfp->uid = -2;
}
dprintk("read_attrs: uid=%d\n", (int)nfp->uid);
......@@ -1493,11 +1490,10 @@ decode_getattr(struct xdr_stream *xdr, struct nfs4_getattr *getattr,
}
READ_BUF(dummy32);
len += (XDR_QUADLEN(dummy32) << 2);
if ((status = nfs_idmap_id(server, IDMAP_TYPE_GROUP,
(char *)p, dummy32, &nfp->gid)) == -1) {
dprintk("read_attrs: gss_get_num failed!\n");
if ((status = nfs_map_group_to_gid(server, (char *)p, dummy32,
&nfp->gid)) < 0) {
dprintk("read_attrs: group-to-gid mapping failed!\n");
nfp->gid = -2;
/* goto out; */
}
dprintk("read_attrs: gid=%d\n", (int)nfp->gid);
}
......
......@@ -52,18 +52,21 @@
#define IDMAP_STATUS_SUCCESS 0x08
struct idmap_msg {
u_int8_t im_type;
u_int8_t im_conv;
char im_name[IDMAP_NAMESZ];
u_int32_t im_id;
u_int8_t im_status;
__u8 im_type;
__u8 im_conv;
char im_name[IDMAP_NAMESZ];
__u32 im_id;
__u8 im_status;
};
#ifdef __KERNEL__
void *nfs_idmap_new(struct nfs_server *);
void nfs_idmap_delete(struct nfs_server *);
int nfs_idmap_id(struct nfs_server *, u_int8_t, char *, u_int, uid_t *);
int nfs_idmap_name(struct nfs_server *, u_int8_t, uid_t, char *, u_int *);
int nfs_map_name_to_uid(struct nfs_server *, const char *, size_t, __u32 *);
int nfs_map_group_to_gid(struct nfs_server *, const char *, size_t, __u32 *);
int nfs_map_uid_to_name(struct nfs_server *, __u32, char *);
int nfs_map_gid_to_group(struct nfs_server *, __u32, char *);
#endif /* __KERNEL__ */
#endif /* NFS_IDMAP_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment