diff options
Diffstat (limited to 'fs/nfs/callback_xdr.c')
| -rw-r--r-- | fs/nfs/callback_xdr.c | 316 | 
1 files changed, 276 insertions, 40 deletions
diff --git a/fs/nfs/callback_xdr.c b/fs/nfs/callback_xdr.c index 05af212f0ed..f4ccfe6521e 100644 --- a/fs/nfs/callback_xdr.c +++ b/fs/nfs/callback_xdr.c @@ -9,9 +9,14 @@  #include <linux/sunrpc/svc.h>  #include <linux/nfs4.h>  #include <linux/nfs_fs.h> +#include <linux/ratelimit.h> +#include <linux/printk.h>  #include <linux/slab.h> +#include <linux/sunrpc/bc_xprt.h>  #include "nfs4_fs.h"  #include "callback.h" +#include "internal.h" +#include "nfs4session.h"  #define CB_OP_TAGLEN_MAXSZ	(512)  #define CB_OP_HDR_RES_MAXSZ	(2 + CB_OP_TAGLEN_MAXSZ) @@ -22,6 +27,8 @@  #define CB_OP_RECALL_RES_MAXSZ	(CB_OP_HDR_RES_MAXSZ)  #if defined(CONFIG_NFS_V4_1) +#define CB_OP_LAYOUTRECALL_RES_MAXSZ	(CB_OP_HDR_RES_MAXSZ) +#define CB_OP_DEVICENOTIFY_RES_MAXSZ	(CB_OP_HDR_RES_MAXSZ)  #define CB_OP_SEQUENCE_RES_MAXSZ	(CB_OP_HDR_RES_MAXSZ + \  					4 + 1 + 3)  #define CB_OP_RECALLANY_RES_MAXSZ	(CB_OP_HDR_RES_MAXSZ) @@ -33,7 +40,8 @@  /* Internal error code */  #define NFS4ERR_RESOURCE_HDR	11050 -typedef __be32 (*callback_process_op_t)(void *, void *); +typedef __be32 (*callback_process_op_t)(void *, void *, +					struct cb_process_state *);  typedef __be32 (*callback_decode_arg_t)(struct svc_rqst *, struct xdr_stream *, void *);  typedef __be32 (*callback_encode_res_t)(struct svc_rqst *, struct xdr_stream *, void *); @@ -68,7 +76,7 @@ static __be32 *read_buf(struct xdr_stream *xdr, int nbytes)  	p = xdr_inline_decode(xdr, nbytes);  	if (unlikely(p == NULL)) -		printk(KERN_WARNING "NFSv4 callback reply buffer overflowed!\n"); +		printk(KERN_WARNING "NFS: NFSv4 callback reply buffer overflowed!\n");  	return p;  } @@ -133,10 +141,10 @@ static __be32 decode_stateid(struct xdr_stream *xdr, nfs4_stateid *stateid)  {  	__be32 *p; -	p = read_buf(xdr, 16); +	p = read_buf(xdr, NFS4_STATEID_SIZE);  	if (unlikely(p == NULL))  		return htonl(NFS4ERR_RESOURCE); -	memcpy(stateid->data, p, 16); +	memcpy(stateid, p, NFS4_STATEID_SIZE);  	return 0;  } @@ -150,7 +158,7 @@ static __be32 decode_compound_hdr_arg(struct xdr_stream *xdr, struct cb_compound  		return status;  	/* We do not like overly long tags! */  	if (hdr->taglen > CB_OP_TAGLEN_MAXSZ - 12) { -		printk("NFSv4 CALLBACK %s: client sent tag of length %u\n", +		printk("NFS: NFSv4 CALLBACK %s: client sent tag of length %u\n",  				__func__, hdr->taglen);  		return htonl(NFS4ERR_RESOURCE);  	} @@ -158,11 +166,11 @@ static __be32 decode_compound_hdr_arg(struct xdr_stream *xdr, struct cb_compound  	if (unlikely(p == NULL))  		return htonl(NFS4ERR_RESOURCE);  	hdr->minorversion = ntohl(*p++); -	/* Check minor version is zero or one. */ -	if (hdr->minorversion <= 1) { -		p++;	/* skip callback_ident */ +	/* Check for minor version support */ +	if (hdr->minorversion <= NFS4_MAX_MINOR_VERSION) { +		hdr->cb_ident = ntohl(*p++); /* ignored by v4.1 and v4.2 */  	} else { -		printk(KERN_WARNING "%s: NFSv4 server callback with " +		pr_warn_ratelimited("NFS: %s: NFSv4 server callback with "  			"illegal minor version %u!\n",  			__func__, hdr->minorversion);  		return htonl(NFS4ERR_MINOR_VERS_MISMATCH); @@ -220,6 +228,157 @@ out:  #if defined(CONFIG_NFS_V4_1) +static __be32 decode_layoutrecall_args(struct svc_rqst *rqstp, +				       struct xdr_stream *xdr, +				       struct cb_layoutrecallargs *args) +{ +	__be32 *p; +	__be32 status = 0; +	uint32_t iomode; + +	args->cbl_addr = svc_addr(rqstp); +	p = read_buf(xdr, 4 * sizeof(uint32_t)); +	if (unlikely(p == NULL)) { +		status = htonl(NFS4ERR_BADXDR); +		goto out; +	} + +	args->cbl_layout_type = ntohl(*p++); +	/* Depite the spec's xdr, iomode really belongs in the FILE switch, +	 * as it is unusable and ignored with the other types. +	 */ +	iomode = ntohl(*p++); +	args->cbl_layoutchanged = ntohl(*p++); +	args->cbl_recall_type = ntohl(*p++); + +	if (args->cbl_recall_type == RETURN_FILE) { +		args->cbl_range.iomode = iomode; +		status = decode_fh(xdr, &args->cbl_fh); +		if (unlikely(status != 0)) +			goto out; + +		p = read_buf(xdr, 2 * sizeof(uint64_t)); +		if (unlikely(p == NULL)) { +			status = htonl(NFS4ERR_BADXDR); +			goto out; +		} +		p = xdr_decode_hyper(p, &args->cbl_range.offset); +		p = xdr_decode_hyper(p, &args->cbl_range.length); +		status = decode_stateid(xdr, &args->cbl_stateid); +		if (unlikely(status != 0)) +			goto out; +	} else if (args->cbl_recall_type == RETURN_FSID) { +		p = read_buf(xdr, 2 * sizeof(uint64_t)); +		if (unlikely(p == NULL)) { +			status = htonl(NFS4ERR_BADXDR); +			goto out; +		} +		p = xdr_decode_hyper(p, &args->cbl_fsid.major); +		p = xdr_decode_hyper(p, &args->cbl_fsid.minor); +	} else if (args->cbl_recall_type != RETURN_ALL) { +		status = htonl(NFS4ERR_BADXDR); +		goto out; +	} +	dprintk("%s: ltype 0x%x iomode %d changed %d recall_type %d\n", +		__func__, +		args->cbl_layout_type, iomode, +		args->cbl_layoutchanged, args->cbl_recall_type); +out: +	dprintk("%s: exit with status = %d\n", __func__, ntohl(status)); +	return status; +} + +static +__be32 decode_devicenotify_args(struct svc_rqst *rqstp, +				struct xdr_stream *xdr, +				struct cb_devicenotifyargs *args) +{ +	__be32 *p; +	__be32 status = 0; +	u32 tmp; +	int n, i; +	args->ndevs = 0; + +	/* Num of device notifications */ +	p = read_buf(xdr, sizeof(uint32_t)); +	if (unlikely(p == NULL)) { +		status = htonl(NFS4ERR_BADXDR); +		goto out; +	} +	n = ntohl(*p++); +	if (n <= 0) +		goto out; +	if (n > ULONG_MAX / sizeof(*args->devs)) { +		status = htonl(NFS4ERR_BADXDR); +		goto out; +	} + +	args->devs = kmalloc(n * sizeof(*args->devs), GFP_KERNEL); +	if (!args->devs) { +		status = htonl(NFS4ERR_DELAY); +		goto out; +	} + +	/* Decode each dev notification */ +	for (i = 0; i < n; i++) { +		struct cb_devicenotifyitem *dev = &args->devs[i]; + +		p = read_buf(xdr, (4 * sizeof(uint32_t)) + NFS4_DEVICEID4_SIZE); +		if (unlikely(p == NULL)) { +			status = htonl(NFS4ERR_BADXDR); +			goto err; +		} + +		tmp = ntohl(*p++);	/* bitmap size */ +		if (tmp != 1) { +			status = htonl(NFS4ERR_INVAL); +			goto err; +		} +		dev->cbd_notify_type = ntohl(*p++); +		if (dev->cbd_notify_type != NOTIFY_DEVICEID4_CHANGE && +		    dev->cbd_notify_type != NOTIFY_DEVICEID4_DELETE) { +			status = htonl(NFS4ERR_INVAL); +			goto err; +		} + +		tmp = ntohl(*p++);	/* opaque size */ +		if (((dev->cbd_notify_type == NOTIFY_DEVICEID4_CHANGE) && +		     (tmp != NFS4_DEVICEID4_SIZE + 8)) || +		    ((dev->cbd_notify_type == NOTIFY_DEVICEID4_DELETE) && +		     (tmp != NFS4_DEVICEID4_SIZE + 4))) { +			status = htonl(NFS4ERR_INVAL); +			goto err; +		} +		dev->cbd_layout_type = ntohl(*p++); +		memcpy(dev->cbd_dev_id.data, p, NFS4_DEVICEID4_SIZE); +		p += XDR_QUADLEN(NFS4_DEVICEID4_SIZE); + +		if (dev->cbd_layout_type == NOTIFY_DEVICEID4_CHANGE) { +			p = read_buf(xdr, sizeof(uint32_t)); +			if (unlikely(p == NULL)) { +				status = htonl(NFS4ERR_BADXDR); +				goto err; +			} +			dev->cbd_immediate = ntohl(*p++); +		} else { +			dev->cbd_immediate = 0; +		} + +		args->ndevs++; + +		dprintk("%s: type %d layout 0x%x immediate %d\n", +			__func__, dev->cbd_notify_type, dev->cbd_layout_type, +			dev->cbd_immediate); +	} +out: +	dprintk("%s: status %d ndevs %d\n", +		__func__, ntohl(status), args->ndevs); +	return status; +err: +	kfree(args->devs); +	goto out; +} +  static __be32 decode_sessionid(struct xdr_stream *xdr,  				 struct nfs4_sessionid *sid)  { @@ -297,9 +456,9 @@ static __be32 decode_cb_sequence_args(struct svc_rqst *rqstp,  	args->csa_nrclists = ntohl(*p++);  	args->csa_rclists = NULL;  	if (args->csa_nrclists) { -		args->csa_rclists = kmalloc(args->csa_nrclists * -					    sizeof(*args->csa_rclists), -					    GFP_KERNEL); +		args->csa_rclists = kmalloc_array(args->csa_nrclists, +						  sizeof(*args->csa_rclists), +						  GFP_KERNEL);  		if (unlikely(args->csa_rclists == NULL))  			goto out; @@ -336,17 +495,18 @@ static __be32 decode_recallany_args(struct svc_rqst *rqstp,  				      struct xdr_stream *xdr,  				      struct cb_recallanyargs *args)  { -	__be32 *p; +	uint32_t bitmap[2]; +	__be32 *p, status;  	args->craa_addr = svc_addr(rqstp);  	p = read_buf(xdr, 4);  	if (unlikely(p == NULL))  		return htonl(NFS4ERR_BADXDR);  	args->craa_objs_to_keep = ntohl(*p++); -	p = read_buf(xdr, 4); -	if (unlikely(p == NULL)) -		return htonl(NFS4ERR_BADXDR); -	args->craa_type_mask = ntohl(*p); +	status = decode_bitmap(xdr, bitmap); +	if (unlikely(status)) +		return status; +	args->craa_type_mask = bitmap[0];  	return 0;  } @@ -361,7 +521,7 @@ static __be32 decode_recallslot_args(struct svc_rqst *rqstp,  	p = read_buf(xdr, 4);  	if (unlikely(p == NULL))  		return htonl(NFS4ERR_BADXDR); -	args->crsa_target_max_slots = ntohl(*p++); +	args->crsa_target_highest_slotid = ntohl(*p++);  	return 0;  } @@ -537,7 +697,7 @@ static __be32 encode_cb_sequence_res(struct svc_rqst *rqstp,  				       const struct cb_sequenceres *res)  {  	__be32 *p; -	unsigned status = res->csr_status; +	__be32 status = res->csr_status;  	if (unlikely(status != 0))  		goto out; @@ -574,11 +734,11 @@ preprocess_nfs41_op(int nop, unsigned int op_nr, struct callback_op **op)  	case OP_CB_SEQUENCE:  	case OP_CB_RECALL_ANY:  	case OP_CB_RECALL_SLOT: +	case OP_CB_LAYOUTRECALL: +	case OP_CB_NOTIFY_DEVICEID:  		*op = &callback_ops[op_nr];  		break; -	case OP_CB_LAYOUTRECALL: -	case OP_CB_NOTIFY_DEVICEID:  	case OP_CB_NOTIFY:  	case OP_CB_PUSH_DELEG:  	case OP_CB_RECALLABLE_OBJ_AVAIL: @@ -593,6 +753,26 @@ preprocess_nfs41_op(int nop, unsigned int op_nr, struct callback_op **op)  	return htonl(NFS_OK);  } +static void nfs4_callback_free_slot(struct nfs4_session *session) +{ +	struct nfs4_slot_table *tbl = &session->bc_slot_table; + +	spin_lock(&tbl->slot_tbl_lock); +	/* +	 * Let the state manager know callback processing done. +	 * A single slot, so highest used slotid is either 0 or -1 +	 */ +	tbl->highest_used_slotid = NFS4_NO_SLOT; +	nfs4_slot_tbl_drain_complete(tbl); +	spin_unlock(&tbl->slot_tbl_lock); +} + +static void nfs4_cb_free_slot(struct cb_process_state *cps) +{ +	if (cps->slotid != NFS4_NO_SLOT) +		nfs4_callback_free_slot(cps->clp->cl_session); +} +  #else /* CONFIG_NFS_V4_1 */  static __be32 @@ -601,8 +781,31 @@ preprocess_nfs41_op(int nop, unsigned int op_nr, struct callback_op **op)  	return htonl(NFS4ERR_MINOR_VERS_MISMATCH);  } +static void nfs4_cb_free_slot(struct cb_process_state *cps) +{ +}  #endif /* CONFIG_NFS_V4_1 */ +#ifdef CONFIG_NFS_V4_2 +static __be32 +preprocess_nfs42_op(int nop, unsigned int op_nr, struct callback_op **op) +{ +	__be32 status = preprocess_nfs41_op(nop, op_nr, op); +	if (status != htonl(NFS4ERR_OP_ILLEGAL)) +		return status; + +	if (op_nr == OP_CB_OFFLOAD) +		return htonl(NFS4ERR_NOTSUPP); +	return htonl(NFS4ERR_OP_ILLEGAL); +} +#else /* CONFIG_NFS_V4_2 */ +static __be32 +preprocess_nfs42_op(int nop, unsigned int op_nr, struct callback_op **op) +{ +	return htonl(NFS4ERR_MINOR_VERS_MISMATCH); +} +#endif /* CONFIG_NFS_V4_2 */ +  static __be32  preprocess_nfs4_op(unsigned int op_nr, struct callback_op **op)  { @@ -618,10 +821,10 @@ preprocess_nfs4_op(unsigned int op_nr, struct callback_op **op)  	return htonl(NFS_OK);  } -static __be32 process_op(uint32_t minorversion, int nop, -		struct svc_rqst *rqstp, +static __be32 process_op(int nop, struct svc_rqst *rqstp,  		struct xdr_stream *xdr_in, void *argp, -		struct xdr_stream *xdr_out, void *resp, int* drc_status) +		struct xdr_stream *xdr_out, void *resp, +		struct cb_process_state *cps)  {  	struct callback_op *op = &callback_ops[0];  	unsigned int op_nr; @@ -635,17 +838,29 @@ static __be32 process_op(uint32_t minorversion, int nop,  		return status;  	dprintk("%s: minorversion=%d nop=%d op_nr=%u\n", -		__func__, minorversion, nop, op_nr); +		__func__, cps->minorversion, nop, op_nr); + +	switch (cps->minorversion) { +	case 0: +		status = preprocess_nfs4_op(op_nr, &op); +		break; +	case 1: +		status = preprocess_nfs41_op(nop, op_nr, &op); +		break; +	case 2: +		status = preprocess_nfs42_op(nop, op_nr, &op); +		break; +	default: +		status = htonl(NFS4ERR_MINOR_VERS_MISMATCH); +	} -	status = minorversion ? preprocess_nfs41_op(nop, op_nr, &op) : -				preprocess_nfs4_op(op_nr, &op);  	if (status == htonl(NFS4ERR_OP_ILLEGAL))  		op_nr = OP_CB_ILLEGAL;  	if (status)  		goto encode_hdr; -	if (*drc_status) { -		status = *drc_status; +	if (cps->drc_status) { +		status = cps->drc_status;  		goto encode_hdr;  	} @@ -653,16 +868,10 @@ static __be32 process_op(uint32_t minorversion, int nop,  	if (maxlen > 0 && maxlen < PAGE_SIZE) {  		status = op->decode_args(rqstp, xdr_in, argp);  		if (likely(status == 0)) -			status = op->process_op(argp, resp); +			status = op->process_op(argp, resp, cps);  	} else  		status = htonl(NFS4ERR_RESOURCE); -	/* Only set by OP_CB_SEQUENCE processing */ -	if (status == htonl(NFS4ERR_RETRY_UNCACHED_REP)) { -		*drc_status = status; -		status = 0; -	} -  encode_hdr:  	res = encode_op_hdr(xdr_out, op_nr, status);  	if (unlikely(res)) @@ -681,8 +890,13 @@ static __be32 nfs4_callback_compound(struct svc_rqst *rqstp, void *argp, void *r  	struct cb_compound_hdr_arg hdr_arg = { 0 };  	struct cb_compound_hdr_res hdr_res = { NULL };  	struct xdr_stream xdr_in, xdr_out; -	__be32 *p; -	__be32 status, drc_status = 0; +	__be32 *p, status; +	struct cb_process_state cps = { +		.drc_status = 0, +		.clp = NULL, +		.slotid = NFS4_NO_SLOT, +		.net = SVC_NET(rqstp), +	};  	unsigned int nops = 0;  	dprintk("%s: start\n", __func__); @@ -696,14 +910,21 @@ static __be32 nfs4_callback_compound(struct svc_rqst *rqstp, void *argp, void *r  	if (status == __constant_htonl(NFS4ERR_RESOURCE))  		return rpc_garbage_args; +	if (hdr_arg.minorversion == 0) { +		cps.clp = nfs4_find_client_ident(SVC_NET(rqstp), hdr_arg.cb_ident); +		if (!cps.clp || !check_gss_callback_principal(cps.clp, rqstp)) +			return rpc_drop_reply; +	} + +	cps.minorversion = hdr_arg.minorversion;  	hdr_res.taglen = hdr_arg.taglen;  	hdr_res.tag = hdr_arg.tag;  	if (encode_compound_hdr_res(&xdr_out, &hdr_res) != 0)  		return rpc_system_err;  	while (status == 0 && nops != hdr_arg.nops) { -		status = process_op(hdr_arg.minorversion, nops, rqstp, -				    &xdr_in, argp, &xdr_out, resp, &drc_status); +		status = process_op(nops, rqstp, &xdr_in, +				    argp, &xdr_out, resp, &cps);  		nops++;  	} @@ -716,6 +937,8 @@ static __be32 nfs4_callback_compound(struct svc_rqst *rqstp, void *argp, void *r  	*hdr_res.status = status;  	*hdr_res.nops = htonl(nops); +	nfs4_cb_free_slot(&cps); +	nfs_put_client(cps.clp);  	dprintk("%s: done, status = %u\n", __func__, ntohl(status));  	return rpc_success;  } @@ -739,6 +962,18 @@ static struct callback_op callback_ops[] = {  		.res_maxsize = CB_OP_RECALL_RES_MAXSZ,  	},  #if defined(CONFIG_NFS_V4_1) +	[OP_CB_LAYOUTRECALL] = { +		.process_op = (callback_process_op_t)nfs4_callback_layoutrecall, +		.decode_args = +			(callback_decode_arg_t)decode_layoutrecall_args, +		.res_maxsize = CB_OP_LAYOUTRECALL_RES_MAXSZ, +	}, +	[OP_CB_NOTIFY_DEVICEID] = { +		.process_op = (callback_process_op_t)nfs4_callback_devicenotify, +		.decode_args = +			(callback_decode_arg_t)decode_devicenotify_args, +		.res_maxsize = CB_OP_DEVICENOTIFY_RES_MAXSZ, +	},  	[OP_CB_SEQUENCE] = {  		.process_op = (callback_process_op_t)nfs4_callback_sequence,  		.decode_args = (callback_decode_arg_t)decode_cb_sequence_args, @@ -792,4 +1027,5 @@ struct svc_version nfs4_callback_version4 = {  	.vs_proc = nfs4_callback_procedures1,  	.vs_xdrsize = NFS4_CALLBACK_XDRSIZE,  	.vs_dispatch = NULL, +	.vs_hidden = 1,  };  | 
