2005-04-16 15:20:36 -07:00
/*
* fs / nfs / idmap . c
*
* UID and GID to name mapping for clients .
*
* Copyright ( c ) 2002 The Regents of the University of Michigan .
* All rights reserved .
*
* Marius Aamodt Eriksen < marius @ umich . edu >
*
* 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 <linux/module.h>
# include <linux/init.h>
# include <linux/types.h>
# include <linux/slab.h>
# include <linux/socket.h>
# include <linux/in.h>
# include <linux/sched.h>
# include <linux/sunrpc/clnt.h>
# include <linux/workqueue.h>
# include <linux/sunrpc/rpc_pipe_fs.h>
# include <linux/nfs_fs_sb.h>
# include <linux/nfs_fs.h>
# include <linux/nfs_idmap.h>
2005-06-22 17:16:21 +00:00
# include "nfs4_fs.h"
2005-04-16 15:20:36 -07:00
# define IDMAP_HASH_SZ 128
struct idmap_hashent {
__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 {
2005-09-23 11:08:25 -04:00
char idmap_path [ 48 ] ;
2005-04-16 15:20:36 -07:00
struct dentry * idmap_dentry ;
wait_queue_head_t idmap_wq ;
struct idmap_msg idmap_im ;
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 __user * , size_t ) ;
static ssize_t idmap_pipe_downcall ( struct file * , const char __user * ,
size_t ) ;
2005-05-05 16:16:09 -07:00
static void idmap_pipe_destroy_msg ( struct rpc_pipe_msg * ) ;
2005-04-16 15:20:36 -07:00
static unsigned int fnvhash32 ( const void * , size_t ) ;
static struct rpc_pipe_ops idmap_upcall_ops = {
. upcall = idmap_pipe_upcall ,
. downcall = idmap_pipe_downcall ,
. destroy_msg = idmap_pipe_destroy_msg ,
} ;
void
nfs_idmap_new ( struct nfs4_client * clp )
{
struct idmap * idmap ;
if ( clp - > cl_idmap ! = NULL )
return ;
if ( ( idmap = kmalloc ( sizeof ( * idmap ) , GFP_KERNEL ) ) = = NULL )
return ;
memset ( idmap , 0 , sizeof ( * idmap ) ) ;
2005-09-23 11:08:25 -04:00
snprintf ( idmap - > idmap_path , sizeof ( idmap - > idmap_path ) ,
" %s/idmap " , clp - > cl_rpcclient - > cl_pathname ) ;
idmap - > idmap_dentry = rpc_mkpipe ( idmap - > idmap_path ,
idmap , & idmap_upcall_ops , 0 ) ;
2005-04-16 15:20:36 -07:00
if ( IS_ERR ( idmap - > idmap_dentry ) ) {
kfree ( idmap ) ;
return ;
}
init_MUTEX ( & idmap - > idmap_lock ) ;
init_MUTEX ( & idmap - > idmap_im_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 ;
clp - > cl_idmap = idmap ;
}
void
nfs_idmap_delete ( struct nfs4_client * clp )
{
struct idmap * idmap = clp - > cl_idmap ;
if ( ! idmap )
return ;
2005-09-23 11:08:25 -04:00
rpc_unlink ( idmap - > idmap_path ) ;
2005-04-16 15:20:36 -07:00
clp - > cl_idmap = NULL ;
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
*/
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_msg * im ;
struct idmap_hashent * he ;
DECLARE_WAITQUEUE ( wq , current ) ;
int ret = - EIO ;
im = & idmap - > idmap_im ;
/*
* 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 ) ;
he = idmap_lookup_name ( h , name , namelen ) ;
if ( he ! = NULL ) {
* id = he - > ih_id ;
ret = 0 ;
goto out ;
}
memset ( im , 0 , sizeof ( * im ) ) ;
memcpy ( im - > im_name , name , namelen ) ;
im - > im_type = h - > h_type ;
im - > im_conv = IDMAP_CONV_NAMETOID ;
memset ( & msg , 0 , sizeof ( msg ) ) ;
msg . data = im ;
msg . len = sizeof ( * im ) ;
add_wait_queue ( & idmap - > idmap_wq , & wq ) ;
if ( rpc_queue_upcall ( idmap - > idmap_dentry - > d_inode , & msg ) < 0 ) {
remove_wait_queue ( & idmap - > idmap_wq , & wq ) ;
goto out ;
}
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
up ( & idmap - > idmap_im_lock ) ;
schedule ( ) ;
current - > state = TASK_RUNNING ;
remove_wait_queue ( & idmap - > idmap_wq , & wq ) ;
down ( & idmap - > idmap_im_lock ) ;
if ( im - > im_status & IDMAP_STATUS_SUCCESS ) {
* id = im - > im_id ;
ret = 0 ;
}
out :
memset ( im , 0 , sizeof ( * im ) ) ;
up ( & idmap - > idmap_im_lock ) ;
up ( & idmap - > idmap_lock ) ;
return ( ret ) ;
}
/*
* ID - > Name
*/
static int
nfs_idmap_name ( struct idmap * idmap , struct idmap_hashtable * h ,
__u32 id , char * name )
{
struct rpc_pipe_msg msg ;
struct idmap_msg * im ;
struct idmap_hashent * he ;
DECLARE_WAITQUEUE ( wq , current ) ;
int ret = - EIO ;
unsigned int len ;
im = & idmap - > idmap_im ;
down ( & idmap - > idmap_lock ) ;
down ( & idmap - > idmap_im_lock ) ;
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 = h - > h_type ;
im - > im_conv = IDMAP_CONV_IDTONAME ;
im - > im_id = id ;
memset ( & msg , 0 , sizeof ( msg ) ) ;
msg . data = im ;
msg . len = sizeof ( * im ) ;
add_wait_queue ( & idmap - > idmap_wq , & wq ) ;
if ( rpc_queue_upcall ( idmap - > idmap_dentry - > d_inode , & msg ) < 0 ) {
remove_wait_queue ( & idmap - > idmap_wq , & wq ) ;
goto out ;
}
set_current_state ( TASK_UNINTERRUPTIBLE ) ;
up ( & idmap - > idmap_im_lock ) ;
schedule ( ) ;
current - > state = TASK_RUNNING ;
remove_wait_queue ( & idmap - > idmap_wq , & wq ) ;
down ( & idmap - > idmap_im_lock ) ;
if ( im - > im_status & IDMAP_STATUS_SUCCESS ) {
if ( ( len = strnlen ( im - > im_name , IDMAP_NAMESZ ) ) = = 0 )
goto out ;
memcpy ( name , im - > im_name , len ) ;
ret = len ;
}
out :
memset ( im , 0 , sizeof ( * im ) ) ;
up ( & idmap - > idmap_im_lock ) ;
up ( & idmap - > idmap_lock ) ;
return ret ;
}
/* RPC pipefs upcall/downcall routines */
static ssize_t
idmap_pipe_upcall ( struct file * filp , struct rpc_pipe_msg * msg ,
char __user * dst , size_t buflen )
{
char * data = ( char * ) msg - > data + msg - > copied ;
ssize_t mlen = msg - > len - msg - > copied ;
ssize_t left ;
if ( mlen > buflen )
mlen = buflen ;
left = copy_to_user ( dst , data , mlen ) ;
if ( left < 0 ) {
msg - > errno = left ;
return left ;
}
mlen - = left ;
msg - > copied + = mlen ;
msg - > errno = 0 ;
return mlen ;
}
static ssize_t
idmap_pipe_downcall ( struct file * filp , const char __user * src , size_t mlen )
{
struct rpc_inode * rpci = RPC_I ( filp - > f_dentry - > d_inode ) ;
struct idmap * idmap = ( struct idmap * ) rpci - > private ;
struct idmap_msg im_in , * im = & idmap - > idmap_im ;
struct idmap_hashtable * h ;
struct idmap_hashent * he = NULL ;
int namelen_in ;
int ret ;
if ( mlen ! = sizeof ( im_in ) )
return ( - ENOSPC ) ;
if ( copy_from_user ( & im_in , src , mlen ) ! = 0 )
return ( - EFAULT ) ;
down ( & idmap - > idmap_im_lock ) ;
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 ;
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 :
/* 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 :
/* 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 :
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 ret ;
}
2005-05-05 16:16:09 -07:00
static void
2005-04-16 15:20:36 -07:00
idmap_pipe_destroy_msg ( struct rpc_pipe_msg * msg )
{
struct idmap_msg * im = msg - > data ;
struct idmap * idmap = container_of ( im , struct idmap , idmap_im ) ;
if ( msg - > errno > = 0 )
return ;
down ( & idmap - > idmap_im_lock ) ;
im - > im_status = IDMAP_STATUS_LOOKUPFAIL ;
wake_up ( & idmap - > idmap_wq ) ;
up ( & idmap - > idmap_im_lock ) ;
}
/*
* Fowler / Noll / Vo hash
* http : //www.isthe.com/chongo/tech/comp/fnv/
*/
# define FNV_P_32 ((unsigned int)0x01000193) /* 16777619 */
# define FNV_1_32 ((unsigned int)0x811c9dc5) /* 2166136261 */
static unsigned int fnvhash32 ( const void * buf , size_t buflen )
{
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 ^ = ( unsigned int ) * p ;
}
return ( hash ) ;
}
int nfs_map_name_to_uid ( struct nfs4_client * clp , const char * name , size_t namelen , __u32 * uid )
{
struct idmap * idmap = clp - > cl_idmap ;
return nfs_idmap_id ( idmap , & idmap - > idmap_user_hash , name , namelen , uid ) ;
}
int nfs_map_group_to_gid ( struct nfs4_client * clp , const char * name , size_t namelen , __u32 * uid )
{
struct idmap * idmap = clp - > cl_idmap ;
return nfs_idmap_id ( idmap , & idmap - > idmap_group_hash , name , namelen , uid ) ;
}
int nfs_map_uid_to_name ( struct nfs4_client * clp , __u32 uid , char * buf )
{
struct idmap * idmap = clp - > cl_idmap ;
return nfs_idmap_name ( idmap , & idmap - > idmap_user_hash , uid , buf ) ;
}
int nfs_map_gid_to_group ( struct nfs4_client * clp , __u32 uid , char * buf )
{
struct idmap * idmap = clp - > cl_idmap ;
return nfs_idmap_name ( idmap , & idmap - > idmap_group_hash , uid , buf ) ;
}