Commit 2fa8729d authored by Trond Myklebust's avatar Trond Myklebust

NFSv4: Add support for a delegation callback server.

Signed-off-by: default avatarTrond Myklebust <trond.myklebust@fys.uio.no>
parent 6d132c2f
......@@ -9,6 +9,7 @@ nfs-y := dir.o file.o inode.o nfs2xdr.o pagelist.o \
nfs-$(CONFIG_ROOT_NFS) += nfsroot.o mount_clnt.o
nfs-$(CONFIG_NFS_V3) += nfs3proc.o nfs3xdr.o
nfs-$(CONFIG_NFS_V4) += nfs4proc.o nfs4xdr.o nfs4state.o nfs4renewd.o \
delegation.o idmap.o
delegation.o idmap.o \
callback.o callback_xdr.o callback_proc.o
nfs-$(CONFIG_NFS_DIRECTIO) += direct.o
nfs-objs := $(nfs-y)
/*
* linux/fs/nfs/callback.c
*
* Copyright (C) 2004 Trond Myklebust
*
* NFSv4 callback handling
*/
#include <linux/config.h>
#include <linux/completion.h>
#include <linux/ip.h>
#include <linux/module.h>
#include <linux/smp_lock.h>
#include <linux/sunrpc/svc.h>
#include <linux/sunrpc/svcsock.h>
#include <linux/nfs_fs.h>
#include "callback.h"
#define NFSDBG_FACILITY NFSDBG_CALLBACK
struct nfs_callback_data {
unsigned int users;
struct svc_serv *serv;
pid_t pid;
struct completion started;
struct completion stopped;
};
static struct nfs_callback_data nfs_callback_info;
static DECLARE_MUTEX(nfs_callback_sema);
static struct svc_program nfs4_callback_program;
unsigned short nfs_callback_tcpport;
/*
* This is the callback kernel thread.
*/
static void nfs_callback_svc(struct svc_rqst *rqstp)
{
struct svc_serv *serv = rqstp->rq_server;
int err;
__module_get(THIS_MODULE);
lock_kernel();
nfs_callback_info.pid = current->pid;
daemonize("nfsv4-svc");
/* Process request with signals blocked, but allow SIGKILL. */
allow_signal(SIGKILL);
complete(&nfs_callback_info.started);
while (nfs_callback_info.users != 0 || !signalled()) {
/*
* Listen for a request on the socket
*/
err = svc_recv(serv, rqstp, MAX_SCHEDULE_TIMEOUT);
if (err == -EAGAIN || err == -EINTR)
continue;
if (err < 0) {
printk(KERN_WARNING
"%s: terminating on error %d\n",
__FUNCTION__, -err);
break;
}
dprintk("%s: request from %u.%u.%u.%u\n", __FUNCTION__,
NIPQUAD(rqstp->rq_addr.sin_addr.s_addr));
svc_process(serv, rqstp);
}
nfs_callback_info.pid = 0;
complete(&nfs_callback_info.stopped);
unlock_kernel();
module_put_and_exit(0);
}
/*
* Bring up the server process if it is not already up.
*/
int nfs_callback_up(void)
{
struct svc_serv *serv;
struct svc_sock *svsk;
int ret = 0;
lock_kernel();
down(&nfs_callback_sema);
if (nfs_callback_info.users++ || nfs_callback_info.pid != 0)
goto out;
init_completion(&nfs_callback_info.started);
init_completion(&nfs_callback_info.stopped);
serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE);
ret = -ENOMEM;
if (!serv)
goto out_err;
/* FIXME: We don't want to register this socket with the portmapper */
ret = svc_makesock(serv, IPPROTO_TCP, 0);
if (ret < 0)
goto out_destroy;
if (!list_empty(&serv->sv_permsocks)) {
svsk = list_entry(serv->sv_permsocks.next,
struct svc_sock, sk_list);
nfs_callback_tcpport = ntohs(inet_sk(svsk->sk_sk)->sport);
dprintk ("Callback port = 0x%x\n", nfs_callback_tcpport);
} else
BUG();
ret = svc_create_thread(nfs_callback_svc, serv);
if (ret < 0)
goto out_destroy;
nfs_callback_info.serv = serv;
wait_for_completion(&nfs_callback_info.started);
out:
up(&nfs_callback_sema);
unlock_kernel();
return ret;
out_destroy:
svc_destroy(serv);
out_err:
nfs_callback_info.users--;
goto out;
}
/*
* Kill the server process if it is not already up.
*/
int nfs_callback_down(void)
{
int ret = 0;
lock_kernel();
down(&nfs_callback_sema);
if (--nfs_callback_info.users || nfs_callback_info.pid == 0)
goto out;
kill_proc(nfs_callback_info.pid, SIGKILL, 1);
wait_for_completion(&nfs_callback_info.stopped);
out:
up(&nfs_callback_sema);
unlock_kernel();
return ret;
}
/*
* AUTH_NULL authentication
*/
static int nfs_callback_null_accept(struct svc_rqst *rqstp, u32 *authp)
{
struct kvec *argv = &rqstp->rq_arg.head[0];
struct kvec *resv = &rqstp->rq_res.head[0];
if (argv->iov_len < 3*4)
return SVC_GARBAGE;
if (svc_getu32(argv) != 0) {
dprintk("svc: bad null cred\n");
*authp = rpc_autherr_badcred;
return SVC_DENIED;
}
if (svc_getu32(argv) != RPC_AUTH_NULL || svc_getu32(argv) != 0) {
dprintk("svc: bad null verf\n");
*authp = rpc_autherr_badverf;
return SVC_DENIED;
}
/* Signal that mapping to nobody uid/gid is required */
rqstp->rq_cred.cr_uid = (uid_t) -1;
rqstp->rq_cred.cr_gid = (gid_t) -1;
rqstp->rq_cred.cr_group_info = groups_alloc(0);
if (rqstp->rq_cred.cr_group_info == NULL)
return SVC_DROP; /* kmalloc failure - client must retry */
/* Put NULL verifier */
svc_putu32(resv, RPC_AUTH_NULL);
svc_putu32(resv, 0);
dprintk("%s: success, returning %d!\n", __FUNCTION__, SVC_OK);
return SVC_OK;
}
static int nfs_callback_null_release(struct svc_rqst *rqstp)
{
if (rqstp->rq_cred.cr_group_info)
put_group_info(rqstp->rq_cred.cr_group_info);
rqstp->rq_cred.cr_group_info = NULL;
return 0; /* don't drop */
}
static struct auth_ops nfs_callback_auth_null = {
.name = "null",
.flavour = RPC_AUTH_NULL,
.accept = nfs_callback_null_accept,
.release = nfs_callback_null_release,
};
/*
* AUTH_SYS authentication
*/
static int nfs_callback_unix_accept(struct svc_rqst *rqstp, u32 *authp)
{
struct kvec *argv = &rqstp->rq_arg.head[0];
struct kvec *resv = &rqstp->rq_res.head[0];
struct svc_cred *cred = &rqstp->rq_cred;
u32 slen, i;
int len = argv->iov_len;
dprintk("%s: start\n", __FUNCTION__);
cred->cr_group_info = NULL;
rqstp->rq_client = NULL;
if ((len -= 3*4) < 0)
return SVC_GARBAGE;
/* Get length, time stamp and machine name */
svc_getu32(argv);
svc_getu32(argv);
slen = XDR_QUADLEN(ntohl(svc_getu32(argv)));
if (slen > 64 || (len -= (slen + 3)*4) < 0)
goto badcred;
argv->iov_base = (void*)((u32*)argv->iov_base + slen);
argv->iov_len -= slen*4;
cred->cr_uid = ntohl(svc_getu32(argv));
cred->cr_gid = ntohl(svc_getu32(argv));
slen = ntohl(svc_getu32(argv));
if (slen > 16 || (len -= (slen + 2)*4) < 0)
goto badcred;
cred->cr_group_info = groups_alloc(slen);
if (cred->cr_group_info == NULL)
return SVC_DROP;
for (i = 0; i < slen; i++)
GROUP_AT(cred->cr_group_info, i) = ntohl(svc_getu32(argv));
if (svc_getu32(argv) != RPC_AUTH_NULL || svc_getu32(argv) != 0) {
*authp = rpc_autherr_badverf;
return SVC_DENIED;
}
/* Put NULL verifier */
svc_putu32(resv, RPC_AUTH_NULL);
svc_putu32(resv, 0);
dprintk("%s: success, returning %d!\n", __FUNCTION__, SVC_OK);
return SVC_OK;
badcred:
*authp = rpc_autherr_badcred;
return SVC_DENIED;
}
static int nfs_callback_unix_release(struct svc_rqst *rqstp)
{
if (rqstp->rq_cred.cr_group_info)
put_group_info(rqstp->rq_cred.cr_group_info);
rqstp->rq_cred.cr_group_info = NULL;
return 0;
}
static struct auth_ops nfs_callback_auth_unix = {
.name = "unix",
.flavour = RPC_AUTH_UNIX,
.accept = nfs_callback_unix_accept,
.release = nfs_callback_unix_release,
};
/*
* Hook the authentication protocol
*/
static int nfs_callback_auth(struct svc_rqst *rqstp, u32 *authp)
{
struct in_addr *addr = &rqstp->rq_addr.sin_addr;
struct nfs4_client *clp;
struct kvec *argv = &rqstp->rq_arg.head[0];
int flavour;
int retval;
/* Don't talk to strangers */
clp = nfs4_find_client(addr);
if (clp == NULL)
return SVC_DROP;
dprintk("%s: %u.%u.%u.%u NFSv4 callback!\n", __FUNCTION__, NIPQUAD(addr));
nfs4_put_client(clp);
flavour = ntohl(svc_getu32(argv));
switch(flavour) {
case RPC_AUTH_NULL:
if (rqstp->rq_proc != CB_NULL) {
*authp = rpc_autherr_tooweak;
retval = SVC_DENIED;
break;
}
rqstp->rq_authop = &nfs_callback_auth_null;
retval = nfs_callback_null_accept(rqstp, authp);
break;
case RPC_AUTH_UNIX:
/* Eat the authentication flavour */
rqstp->rq_authop = &nfs_callback_auth_unix;
retval = nfs_callback_unix_accept(rqstp, authp);
break;
default:
/* FIXME: need to add RPCSEC_GSS upcalls */
#if 0
svc_ungetu32(argv);
retval = svc_authenticate(rqstp, authp);
#else
*authp = rpc_autherr_rejectedcred;
retval = SVC_DENIED;
#endif
}
dprintk("%s: flavour %d returning error %d\n", __FUNCTION__, flavour, retval);
return retval;
}
/*
* Define NFS4 callback program
*/
extern struct svc_version nfs4_callback_version1;
static struct svc_version *nfs4_callback_version[] = {
[1] = &nfs4_callback_version1,
};
static struct svc_stat nfs4_callback_stats;
static struct svc_program nfs4_callback_program = {
.pg_prog = NFS4_CALLBACK, /* RPC service number */
.pg_nvers = ARRAY_SIZE(nfs4_callback_version), /* Number of entries */
.pg_vers = nfs4_callback_version, /* version table */
.pg_name = "NFSv4 callback", /* service name */
.pg_class = "nfs", /* authentication class */
.pg_stats = &nfs4_callback_stats,
.pg_authenticate = nfs_callback_auth,
};
/*
* linux/fs/nfs/callback.h
*
* Copyright (C) 2004 Trond Myklebust
*
* NFSv4 callback definitions
*/
#ifndef __LINUX_FS_NFS_CALLBACK_H
#define __LINUX_FS_NFS_CALLBACK_H
#define NFS4_CALLBACK 0x40000000
#define NFS4_CALLBACK_XDRSIZE 2048
#define NFS4_CALLBACK_BUFSIZE (1024 + NFS4_CALLBACK_XDRSIZE)
enum nfs4_callback_procnum {
CB_NULL = 0,
CB_COMPOUND = 1,
};
enum nfs4_callback_opnum {
OP_CB_GETATTR = 3,
OP_CB_RECALL = 4,
OP_CB_ILLEGAL = 10044,
};
struct cb_compound_hdr_arg {
int taglen;
const char *tag;
unsigned int callback_ident;
unsigned nops;
};
struct cb_compound_hdr_res {
uint32_t *status;
int taglen;
const char *tag;
uint32_t *nops;
};
struct cb_getattrargs {
struct sockaddr_in *addr;
struct nfs_fh fh;
uint32_t bitmap[2];
};
struct cb_getattrres {
uint32_t status;
uint32_t bitmap[2];
uint64_t size;
uint64_t change_attr;
struct timespec ctime;
struct timespec mtime;
};
struct cb_recallargs {
struct sockaddr_in *addr;
struct nfs_fh fh;
nfs4_stateid stateid;
uint32_t truncate;
};
extern unsigned nfs4_callback_getattr(struct cb_getattrargs *args, struct cb_getattrres *res);
extern unsigned nfs4_callback_recall(struct cb_recallargs *args, void *dummy);
extern int nfs_callback_up(void);
extern int nfs_callback_down(void);
extern unsigned short nfs_callback_tcpport;
#endif /* __LINUX_FS_NFS_CALLBACK_H */
/*
* linux/fs/nfs/callback_proc.c
*
* Copyright (C) 2004 Trond Myklebust
*
* NFSv4 callback procedures
*/
#include <linux/config.h>
#include <linux/nfs4.h>
#include <linux/nfs_fs.h>
#include "callback.h"
#include "delegation.h"
#define NFSDBG_FACILITY NFSDBG_CALLBACK
unsigned nfs4_callback_getattr(struct cb_getattrargs *args, struct cb_getattrres *res)
{
struct nfs4_client *clp;
struct nfs_delegation *delegation;
struct nfs_inode *nfsi;
struct inode *inode;
res->bitmap[0] = res->bitmap[1] = 0;
res->status = htonl(NFS4ERR_BADHANDLE);
clp = nfs4_find_client(&args->addr->sin_addr);
if (clp == NULL)
goto out;
inode = nfs_delegation_find_inode(clp, &args->fh);
if (inode == NULL)
goto out_putclient;
nfsi = NFS_I(inode);
down_read(&nfsi->rwsem);
delegation = nfsi->delegation;
if (delegation == NULL || (delegation->type & FMODE_WRITE) == 0)
goto out_iput;
res->size = i_size_read(inode);
res->change_attr = NFS_CHANGE_ATTR(inode);
res->ctime = inode->i_ctime;
res->mtime = inode->i_mtime;
res->bitmap[0] = (FATTR4_WORD0_CHANGE|FATTR4_WORD0_SIZE) &
args->bitmap[0];
res->bitmap[1] = (FATTR4_WORD1_TIME_METADATA|FATTR4_WORD1_TIME_MODIFY) &
args->bitmap[1];
res->status = 0;
out_iput:
up_read(&nfsi->rwsem);
iput(inode);
out_putclient:
nfs4_put_client(clp);
out:
dprintk("%s: exit with status = %d\n", __FUNCTION__, ntohl(res->status));
return res->status;
}
unsigned nfs4_callback_recall(struct cb_recallargs *args, void *dummy)
{
struct nfs4_client *clp;
struct inode *inode;
unsigned res;
res = htonl(NFS4ERR_BADHANDLE);
clp = nfs4_find_client(&args->addr->sin_addr);
if (clp == NULL)
goto out;
inode = nfs_delegation_find_inode(clp, &args->fh);
if (inode == NULL)
goto out_putclient;
/* Set up a helper thread to actually return the delegation */
switch(nfs_async_inode_return_delegation(inode, &args->stateid)) {
case 0:
res = 0;
break;
case -ENOENT:
res = htonl(NFS4ERR_BAD_STATEID);
break;
default:
res = htonl(NFS4ERR_RESOURCE);
}
iput(inode);
out_putclient:
nfs4_put_client(clp);
out:
dprintk("%s: exit with status = %d\n", __FUNCTION__, ntohl(res));
return res;
}
/*
* linux/fs/nfs/callback_xdr.c
*
* Copyright (C) 2004 Trond Myklebust
*
* NFSv4 callback encode/decode procedures
*/
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sunrpc/svc.h>
#include <linux/nfs4.h>
#include <linux/nfs_fs.h>
#include "callback.h"
#define CB_OP_TAGLEN_MAXSZ (512)
#define CB_OP_HDR_RES_MAXSZ (2 + CB_OP_TAGLEN_MAXSZ)
#define CB_OP_GETATTR_BITMAP_MAXSZ (4)
#define CB_OP_GETATTR_RES_MAXSZ (CB_OP_HDR_RES_MAXSZ + \
CB_OP_GETATTR_BITMAP_MAXSZ + \
2 + 2 + 3 + 3)
#define CB_OP_RECALL_RES_MAXSZ (CB_OP_HDR_RES_MAXSZ)
#define NFSDBG_FACILITY NFSDBG_CALLBACK
typedef unsigned (*callback_process_op_t)(void *, void *);
typedef unsigned (*callback_decode_arg_t)(struct svc_rqst *, struct xdr_stream *, void *);
typedef unsigned (*callback_encode_res_t)(struct svc_rqst *, struct xdr_stream *, void *);
struct callback_op {
callback_process_op_t process_op;
callback_decode_arg_t decode_args;
callback_encode_res_t encode_res;
long res_maxsize;
};
static struct callback_op callback_ops[];
static int nfs4_callback_null(struct svc_rqst *rqstp, void *argp, void *resp)
{
return htonl(NFS4_OK);
}
static int nfs4_decode_void(struct svc_rqst *rqstp, uint32_t *p, void *dummy)
{
return xdr_argsize_check(rqstp, p);
}
static int nfs4_encode_void(struct svc_rqst *rqstp, uint32_t *p, void *dummy)
{
return xdr_ressize_check(rqstp, p);
}
static uint32_t *read_buf(struct xdr_stream *xdr, int nbytes)
{
uint32_t *p;
p = xdr_inline_decode(xdr, nbytes);
if (unlikely(p == NULL))
printk(KERN_WARNING "NFSv4 callback reply buffer overflowed!\n");
return p;
}
static unsigned decode_string(struct xdr_stream *xdr, unsigned int *len, const char **str)
{
uint32_t *p;
p = read_buf(xdr, 4);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
*len = ntohl(*p);
if (*len != 0) {
p = read_buf(xdr, *len);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
*str = (const char *)p;
} else
*str = NULL;
return 0;
}
static unsigned decode_fh(struct xdr_stream *xdr, struct nfs_fh *fh)
{
uint32_t *p;
p = read_buf(xdr, 4);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
fh->size = ntohl(*p);
if (fh->size > NFS4_FHSIZE)
return htonl(NFS4ERR_BADHANDLE);
p = read_buf(xdr, fh->size);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
memcpy(&fh->data[0], p, fh->size);
memset(&fh->data[fh->size], 0, sizeof(fh->data) - fh->size);
return 0;
}
static unsigned decode_bitmap(struct xdr_stream *xdr, uint32_t *bitmap)
{
uint32_t *p;
unsigned int attrlen;
p = read_buf(xdr, 4);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
attrlen = ntohl(*p);
p = read_buf(xdr, attrlen << 2);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
if (likely(attrlen > 0))
bitmap[0] = ntohl(*p++);
if (attrlen > 1)
bitmap[1] = ntohl(*p);
return 0;
}
static unsigned decode_stateid(struct xdr_stream *xdr, nfs4_stateid *stateid)
{
uint32_t *p;
p = read_buf(xdr, 16);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
memcpy(stateid->data, p, 16);
return 0;
}
static unsigned decode_compound_hdr_arg(struct xdr_stream *xdr, struct cb_compound_hdr_arg *hdr)
{
uint32_t *p;
unsigned int minor_version;
unsigned status;
status = decode_string(xdr, &hdr->taglen, &hdr->tag);
if (unlikely(status != 0))
return status;
/* We do not like overly long tags! */
if (hdr->taglen > CB_OP_TAGLEN_MAXSZ-12 || hdr->taglen < 0) {
printk("NFSv4 CALLBACK %s: client sent tag of length %u\n",
__FUNCTION__, hdr->taglen);
return htonl(NFS4ERR_RESOURCE);
}
p = read_buf(xdr, 12);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
minor_version = ntohl(*p++);
/* Check minor version is zero. */
if (minor_version != 0) {
printk(KERN_WARNING "%s: NFSv4 server callback with illegal minor version %u!\n",
__FUNCTION__, minor_version);
return htonl(NFS4ERR_MINOR_VERS_MISMATCH);
}
hdr->callback_ident = ntohl(*p++);
hdr->nops = ntohl(*p);
return 0;
}
static unsigned decode_op_hdr(struct xdr_stream *xdr, unsigned int *op)
{
uint32_t *p;
p = read_buf(xdr, 4);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
*op = ntohl(*p);
return 0;
}
static unsigned decode_getattr_args(struct svc_rqst *rqstp, struct xdr_stream *xdr, struct cb_getattrargs *args)
{
unsigned status;
status = decode_fh(xdr, &args->fh);
if (unlikely(status != 0))
goto out;
args->addr = &rqstp->rq_addr;
status = decode_bitmap(xdr, args->bitmap);
out:
dprintk("%s: exit with status = %d\n", __FUNCTION__, status);
return status;
}
static unsigned decode_recall_args(struct svc_rqst *rqstp, struct xdr_stream *xdr, struct cb_recallargs *args)
{
uint32_t *p;
unsigned status;
args->addr = &rqstp->rq_addr;
status = decode_stateid(xdr, &args->stateid);
if (unlikely(status != 0))
goto out;
p = read_buf(xdr, 4);
if (unlikely(p == NULL)) {
status = htonl(NFS4ERR_RESOURCE);
goto out;
}
args->truncate = ntohl(*p);
status = decode_fh(xdr, &args->fh);
out:
dprintk("%s: exit with status = %d\n", __FUNCTION__, status);
return 0;
}
static unsigned encode_string(struct xdr_stream *xdr, unsigned int len, const char *str)
{
uint32_t *p;
p = xdr_reserve_space(xdr, 4 + len);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
xdr_encode_opaque(p, str, len);
return 0;
}
#define CB_SUPPORTED_ATTR0 (FATTR4_WORD0_CHANGE|FATTR4_WORD0_SIZE)
#define CB_SUPPORTED_ATTR1 (FATTR4_WORD1_TIME_METADATA|FATTR4_WORD1_TIME_MODIFY)
static unsigned encode_attr_bitmap(struct xdr_stream *xdr, const uint32_t *bitmap, uint32_t **savep)
{
uint32_t bm[2];
uint32_t *p;
bm[0] = htonl(bitmap[0] & CB_SUPPORTED_ATTR0);
bm[1] = htonl(bitmap[1] & CB_SUPPORTED_ATTR1);
if (bm[1] != 0) {
p = xdr_reserve_space(xdr, 16);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
*p++ = htonl(2);
*p++ = bm[0];
*p++ = bm[1];
} else if (bm[0] != 0) {
p = xdr_reserve_space(xdr, 12);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
*p++ = htonl(1);
*p++ = bm[0];
} else {
p = xdr_reserve_space(xdr, 8);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
*p++ = htonl(0);
}
*savep = p;
return 0;
}
static unsigned encode_attr_change(struct xdr_stream *xdr, const uint32_t *bitmap, uint64_t change)
{
uint32_t *p;
if (!(bitmap[0] & FATTR4_WORD0_CHANGE))
return 0;
p = xdr_reserve_space(xdr, 8);
if (unlikely(p == 0))
return htonl(NFS4ERR_RESOURCE);
p = xdr_encode_hyper(p, change);
return 0;
}
static unsigned encode_attr_size(struct xdr_stream *xdr, const uint32_t *bitmap, uint64_t size)
{
uint32_t *p;
if (!(bitmap[0] & FATTR4_WORD0_SIZE))
return 0;
p = xdr_reserve_space(xdr, 8);
if (unlikely(p == 0))
return htonl(NFS4ERR_RESOURCE);
p = xdr_encode_hyper(p, size);
return 0;
}
static unsigned encode_attr_time(struct xdr_stream *xdr, const struct timespec *time)
{
uint32_t *p;
p = xdr_reserve_space(xdr, 12);
if (unlikely(p == 0))
return htonl(NFS4ERR_RESOURCE);
p = xdr_encode_hyper(p, time->tv_sec);
*p = htonl(time->tv_nsec);
return 0;
}
static unsigned encode_attr_ctime(struct xdr_stream *xdr, const uint32_t *bitmap, const struct timespec *time)
{
if (!(bitmap[1] & FATTR4_WORD1_TIME_METADATA))
return 0;
return encode_attr_time(xdr,time);
}
static unsigned encode_attr_mtime(struct xdr_stream *xdr, const uint32_t *bitmap, const struct timespec *time)
{
if (!(bitmap[1] & FATTR4_WORD1_TIME_MODIFY))
return 0;
return encode_attr_time(xdr,time);
}
static unsigned encode_compound_hdr_res(struct xdr_stream *xdr, struct cb_compound_hdr_res *hdr)
{
unsigned status;
hdr->status = xdr_reserve_space(xdr, 4);
if (unlikely(hdr->status == NULL))
return htonl(NFS4ERR_RESOURCE);
status = encode_string(xdr, hdr->taglen, hdr->tag);
if (unlikely(status != 0))
return status;
hdr->nops = xdr_reserve_space(xdr, 4);
if (unlikely(hdr->nops == NULL))
return htonl(NFS4ERR_RESOURCE);
return 0;
}
static unsigned encode_op_hdr(struct xdr_stream *xdr, uint32_t op, uint32_t res)
{
uint32_t *p;
p = xdr_reserve_space(xdr, 8);
if (unlikely(p == NULL))
return htonl(NFS4ERR_RESOURCE);
*p++ = htonl(op);
*p = res;
return 0;
}
static unsigned encode_getattr_res(struct svc_rqst *rqstp, struct xdr_stream *xdr, const struct cb_getattrres *res)
{
uint32_t *savep;
unsigned status = res->status;
if (unlikely(status != 0))
goto out;
status = encode_attr_bitmap(xdr, res->bitmap, &savep);
if (unlikely(status != 0))
goto out;
status = encode_attr_change(xdr, res->bitmap, res->change_attr);
if (unlikely(status != 0))
goto out;
status = encode_attr_size(xdr, res->bitmap, res->size);
if (unlikely(status != 0))
goto out;
status = encode_attr_ctime(xdr, res->bitmap, &res->ctime);
if (unlikely(status != 0))
goto out;
status = encode_attr_mtime(xdr, res->bitmap, &res->mtime);
*savep = htonl((unsigned int)((char *)xdr->p - (char *)(savep+1)));
out:
dprintk("%s: exit with status = %d\n", __FUNCTION__, status);
return status;
}
static unsigned process_op(struct svc_rqst *rqstp,
struct xdr_stream *xdr_in, void *argp,
struct xdr_stream *xdr_out, void *resp)
{
struct callback_op *op;
unsigned int op_nr;
unsigned int status = 0;
long maxlen;
unsigned res;
dprintk("%s: start\n", __FUNCTION__);
status = decode_op_hdr(xdr_in, &op_nr);
if (unlikely(status != 0)) {
op_nr = OP_CB_ILLEGAL;
op = &callback_ops[0];
} else if (unlikely(op_nr != OP_CB_GETATTR && op_nr != OP_CB_RECALL)) {
op_nr = OP_CB_ILLEGAL;
op = &callback_ops[0];
status = htonl(NFS4ERR_OP_ILLEGAL);
} else
op = &callback_ops[op_nr];
maxlen = xdr_out->end - xdr_out->p;
if (maxlen > 0 && maxlen < PAGE_SIZE) {
if (likely(status == 0 && op->decode_args != NULL))
status = op->decode_args(rqstp, xdr_in, argp);
if (likely(status == 0 && op->process_op != NULL))
status = op->process_op(argp, resp);
} else
status = htonl(NFS4ERR_RESOURCE);
res = encode_op_hdr(xdr_out, op_nr, status);
if (status == 0)
status = res;
if (op->encode_res != NULL && status == 0)
status = op->encode_res(rqstp, xdr_out, resp);
dprintk("%s: done, status = %d\n", __FUNCTION__, status);
return status;
}
/*
* Decode, process and encode a COMPOUND
*/
static int nfs4_callback_compound(struct svc_rqst *rqstp, void *argp, void *resp)
{
struct cb_compound_hdr_arg hdr_arg;
struct cb_compound_hdr_res hdr_res;
struct xdr_stream xdr_in, xdr_out;
uint32_t *p;
unsigned int status;
unsigned int nops = 1;
dprintk("%s: start\n", __FUNCTION__);
xdr_init_decode(&xdr_in, &rqstp->rq_arg, rqstp->rq_arg.head[0].iov_base);
p = (uint32_t*)((char *)rqstp->rq_res.head[0].iov_base + rqstp->rq_res.head[0].iov_len);
rqstp->rq_res.head[0].iov_len = PAGE_SIZE;
xdr_init_encode(&xdr_out, &rqstp->rq_res, p);
decode_compound_hdr_arg(&xdr_in, &hdr_arg);
hdr_res.taglen = hdr_arg.taglen;
hdr_res.tag = hdr_arg.tag;
encode_compound_hdr_res(&xdr_out, &hdr_res);
for (;;) {
status = process_op(rqstp, &xdr_in, argp, &xdr_out, resp);
if (status != 0)
break;
if (nops == hdr_arg.nops)
break;
nops++;
}
*hdr_res.status = status;
*hdr_res.nops = htonl(nops);
dprintk("%s: done, status = %u\n", __FUNCTION__, status);
return rpc_success;
}
/*
* Define NFS4 callback COMPOUND ops.
*/
static struct callback_op callback_ops[] = {
[0] = {
.res_maxsize = CB_OP_HDR_RES_MAXSZ,
},
[OP_CB_GETATTR] = {
.process_op = (callback_process_op_t)nfs4_callback_getattr,
.decode_args = (callback_decode_arg_t)decode_getattr_args,
.encode_res = (callback_encode_res_t)encode_getattr_res,
.res_maxsize = CB_OP_GETATTR_RES_MAXSZ,
},
[OP_CB_RECALL] = {
.process_op = (callback_process_op_t)nfs4_callback_recall,
.decode_args = (callback_decode_arg_t)decode_recall_args,
.res_maxsize = CB_OP_RECALL_RES_MAXSZ,
}
};
/*
* Define NFS4 callback procedures
*/
static struct svc_procedure nfs4_callback_procedures1[] = {
[CB_NULL] = {
.pc_func = nfs4_callback_null,
.pc_decode = (kxdrproc_t)nfs4_decode_void,
.pc_encode = (kxdrproc_t)nfs4_encode_void,
.pc_xdrressize = 1,
},
[CB_COMPOUND] = {
.pc_func = nfs4_callback_compound,
.pc_encode = (kxdrproc_t)nfs4_encode_void,
.pc_argsize = 256,
.pc_ressize = 256,
.pc_xdrressize = NFS4_CALLBACK_BUFSIZE,
}
};
struct svc_version nfs4_callback_version1 = {
.vs_vers = 1,
.vs_nproc = ARRAY_SIZE(nfs4_callback_procedures1),
.vs_proc = nfs4_callback_procedures1,
.vs_xdrsize = NFS4_CALLBACK_XDRSIZE,
.vs_dispatch = NULL,
};
......@@ -6,10 +6,15 @@
* NFS file delegation management
*
*/
#include <linux/config.h>
#include <linux/completion.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/nfs4.h>
#include <linux/nfs_fs.h>
#include <linux/nfs_xdr.h>
#include <linux/spinlock.h>
#include "delegation.h"
......@@ -141,6 +146,75 @@ void nfs_return_all_delegations(struct super_block *sb)
spin_unlock(&clp->cl_lock);
}
struct recall_threadargs {
struct inode *inode;
struct nfs4_client *clp;
const nfs4_stateid *stateid;
struct completion started;
int result;
};
static int recall_thread(void *data)
{
struct recall_threadargs *args = (struct recall_threadargs *)data;
struct inode *inode = igrab(args->inode);
struct nfs4_client *clp = NFS_SERVER(inode)->nfs4_state;
struct nfs_inode *nfsi = NFS_I(inode);
struct nfs_delegation *delegation;
daemonize("nfsv4-delegreturn");
nfs_msync_inode(inode);
down_read(&clp->cl_sem);
down_write(&nfsi->rwsem);
spin_lock(&clp->cl_lock);
delegation = nfsi->delegation;
if (delegation != NULL && memcmp(delegation->stateid.data,
args->stateid->data,
sizeof(delegation->stateid.data)) == 0) {
list_del_init(&delegation->super_list);
nfsi->delegation = NULL;
args->result = 0;
} else {
delegation = NULL;
args->result = -ENOENT;
}
spin_unlock(&clp->cl_lock);
complete(&args->started);
up_write(&nfsi->rwsem);
up_read(&clp->cl_sem);
nfs_msync_inode(inode);
if (delegation != NULL)
nfs_do_return_delegation(inode, delegation);
iput(inode);
module_put_and_exit(0);
}
/*
* Asynchronous delegation recall!
*/
int nfs_async_inode_return_delegation(struct inode *inode, const nfs4_stateid *stateid)
{
struct recall_threadargs data = {
.inode = inode,
.stateid = stateid,
};
int status;
init_completion(&data.started);
__module_get(THIS_MODULE);
status = kernel_thread(recall_thread, &data, CLONE_KERNEL);
if (status < 0)
goto out_module_put;
wait_for_completion(&data.started);
return data.result;
out_module_put:
module_put(THIS_MODULE);
return status;
}
/*
* Retrieve the inode associated with a delegation
*/
......
......@@ -22,6 +22,7 @@ struct nfs_delegation {
int nfs_inode_set_delegation(struct inode *inode, struct rpc_cred *cred, struct nfs_openres *res);
int nfs_inode_return_delegation(struct inode *inode);
int nfs_async_inode_return_delegation(struct inode *inode, const nfs4_stateid *stateid);
struct inode *nfs_delegation_find_inode(struct nfs4_client *clp, const struct nfs_fh *fhandle);
void nfs_return_all_delegations(struct super_block *sb);
......
......@@ -46,6 +46,8 @@
#include <linux/workqueue.h>
#include <linux/bitops.h>
#include "callback.h"
#define OPENOWNER_POOL_SIZE 8
static spinlock_t state_spinlock = SPIN_LOCK_UNLOCKED;
......@@ -94,22 +96,26 @@ nfs4_alloc_client(struct in_addr *addr)
{
struct nfs4_client *clp;
if ((clp = kmalloc(sizeof(*clp), GFP_KERNEL))) {
memset(clp, 0, sizeof(*clp));
memcpy(&clp->cl_addr, addr, sizeof(clp->cl_addr));
init_rwsem(&clp->cl_sem);
INIT_LIST_HEAD(&clp->cl_delegations);
INIT_LIST_HEAD(&clp->cl_state_owners);
INIT_LIST_HEAD(&clp->cl_unused);
spin_lock_init(&clp->cl_lock);
atomic_set(&clp->cl_count, 1);
INIT_WORK(&clp->cl_recoverd, nfs4_recover_state, clp);
INIT_WORK(&clp->cl_renewd, nfs4_renew_state, clp);
INIT_LIST_HEAD(&clp->cl_superblocks);
init_waitqueue_head(&clp->cl_waitq);
rpc_init_wait_queue(&clp->cl_rpcwaitq, "NFS4 client");
clp->cl_state = 1 << NFS4CLNT_OK;
if (nfs_callback_up() < 0)
return NULL;
if ((clp = kmalloc(sizeof(*clp), GFP_KERNEL)) == NULL) {
nfs_callback_down();
return NULL;
}
memset(clp, 0, sizeof(*clp));
memcpy(&clp->cl_addr, addr, sizeof(clp->cl_addr));
init_rwsem(&clp->cl_sem);
INIT_LIST_HEAD(&clp->cl_delegations);
INIT_LIST_HEAD(&clp->cl_state_owners);
INIT_LIST_HEAD(&clp->cl_unused);
spin_lock_init(&clp->cl_lock);
atomic_set(&clp->cl_count, 1);
INIT_WORK(&clp->cl_recoverd, nfs4_recover_state, clp);
INIT_WORK(&clp->cl_renewd, nfs4_renew_state, clp);
INIT_LIST_HEAD(&clp->cl_superblocks);
init_waitqueue_head(&clp->cl_waitq);
rpc_init_wait_queue(&clp->cl_rpcwaitq, "NFS4 client");
clp->cl_state = 1 << NFS4CLNT_OK;
return clp;
}
......@@ -132,25 +138,52 @@ nfs4_free_client(struct nfs4_client *clp)
if (clp->cl_rpcclient)
rpc_shutdown_client(clp->cl_rpcclient);
kfree(clp);
nfs_callback_down();
}
static struct nfs4_client *__nfs4_find_client(struct in_addr *addr)
{
struct nfs4_client *clp;
list_for_each_entry(clp, &nfs4_clientid_list, cl_servers) {
if (memcmp(&clp->cl_addr, addr, sizeof(clp->cl_addr)) == 0) {
atomic_inc(&clp->cl_count);
return clp;
}
}
return NULL;
}
struct nfs4_client *nfs4_find_client(struct in_addr *addr)
{
struct nfs4_client *clp;
spin_lock(&state_spinlock);
clp = __nfs4_find_client(addr);
spin_unlock(&state_spinlock);
return clp;
}
struct nfs4_client *
nfs4_get_client(struct in_addr *addr)
{
struct nfs4_client *new, *clp = NULL;
struct nfs4_client *clp, *new = NULL;
new = nfs4_alloc_client(addr);
spin_lock(&state_spinlock);
list_for_each_entry(clp, &nfs4_clientid_list, cl_servers) {
if (memcmp(&clp->cl_addr, addr, sizeof(clp->cl_addr)) == 0)
goto found;
for (;;) {
clp = __nfs4_find_client(addr);
if (clp != NULL)
break;
clp = new;
if (clp != NULL) {
list_add(&clp->cl_servers, &nfs4_clientid_list);
new = NULL;
break;
}
spin_unlock(&state_spinlock);
new = nfs4_alloc_client(addr);
spin_lock(&state_spinlock);
if (new == NULL)
break;
}
if (new)
list_add(&new->cl_servers, &nfs4_clientid_list);
spin_unlock(&state_spinlock);
return new;
found:
atomic_inc(&clp->cl_count);
spin_unlock(&state_spinlock);
if (new)
nfs4_free_client(new);
......
......@@ -686,6 +686,7 @@ extern void destroy_nfsv4_state(struct nfs_server *);
extern struct nfs4_client *nfs4_get_client(struct in_addr *);
extern void nfs4_put_client(struct nfs4_client *clp);
extern int nfs4_init_client(struct nfs4_client *clp);
extern struct nfs4_client *nfs4_find_client(struct in_addr *);
extern u32 nfs4_alloc_lockowner_id(struct nfs4_client *);
extern struct nfs4_state_owner * nfs4_get_state_owner(struct nfs_server *, struct rpc_cred *);
......@@ -729,6 +730,7 @@ struct nfs4_mount_data;
#define NFSDBG_XDR 0x0020
#define NFSDBG_FILE 0x0040
#define NFSDBG_ROOT 0x0080
#define NFSDBG_CALLBACK 0x0100
#define NFSDBG_ALL 0xFFFF
#ifdef __KERNEL__
......
......@@ -87,6 +87,14 @@ static inline u32 svc_getu32(struct kvec *iov)
iov->iov_len -= sizeof(u32);
return val;
}
static inline void svc_ungetu32(struct kvec *iov)
{
u32 *vp = (u32 *)iov->iov_base;
iov->iov_base = (void *)(vp - 1);
iov->iov_len += sizeof(*vp);
}
static inline void svc_putu32(struct kvec *iov, u32 val)
{
u32 *vp = iov->iov_base + iov->iov_len;
......@@ -243,6 +251,8 @@ struct svc_program {
char * pg_name; /* service name */
char * pg_class; /* class name: services sharing authentication */
struct svc_stat * pg_stats; /* rpc statistics */
/* Override authentication. NULL means use default */
int (*pg_authenticate)(struct svc_rqst *, u32 *);
};
/*
......
......@@ -89,6 +89,7 @@ EXPORT_SYMBOL(svc_makesock);
EXPORT_SYMBOL(svc_reserve);
EXPORT_SYMBOL(svc_auth_register);
EXPORT_SYMBOL(auth_domain_lookup);
EXPORT_SYMBOL(svc_authenticate);
/* RPC statistics */
#ifdef CONFIG_PROC_FS
......
......@@ -263,6 +263,7 @@ svc_process(struct svc_serv *serv, struct svc_rqst *rqstp)
u32 *statp;
u32 dir, prog, vers, proc,
auth_stat, rpc_stat;
int auth_res;
rpc_stat = rpc_success;
......@@ -304,12 +305,17 @@ svc_process(struct svc_serv *serv, struct svc_rqst *rqstp)
rqstp->rq_vers = vers = ntohl(svc_getu32(argv)); /* version number */
rqstp->rq_proc = proc = ntohl(svc_getu32(argv)); /* procedure number */
progp = serv->sv_program;
/*
* Decode auth data, and add verifier to reply buffer.
* We do this before anything else in order to get a decent
* auth verifier.
*/
switch (svc_authenticate(rqstp, &auth_stat)) {
if (progp->pg_authenticate != NULL)
auth_res = progp->pg_authenticate(rqstp, &auth_stat);
else
auth_res = svc_authenticate(rqstp, &auth_stat);
switch (auth_res) {
case SVC_OK:
break;
case SVC_GARBAGE:
......@@ -326,7 +332,6 @@ svc_process(struct svc_serv *serv, struct svc_rqst *rqstp)
goto sendit;
}
progp = serv->sv_program;
if (prog != progp->pg_prog)
goto err_bad_prog;
......
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