diff options
Diffstat (limited to 'fs/lockd')
| -rw-r--r-- | fs/lockd/Makefile | 6 | ||||
| -rw-r--r-- | fs/lockd/clnt4xdr.c | 599 | ||||
| -rw-r--r-- | fs/lockd/clntlock.c | 287 | ||||
| -rw-r--r-- | fs/lockd/clntproc.c | 630 | ||||
| -rw-r--r-- | fs/lockd/clntxdr.c | 621 | ||||
| -rw-r--r-- | fs/lockd/grace.c | 65 | ||||
| -rw-r--r-- | fs/lockd/host.c | 731 | ||||
| -rw-r--r-- | fs/lockd/mon.c | 655 | ||||
| -rw-r--r-- | fs/lockd/netns.h | 23 | ||||
| -rw-r--r-- | fs/lockd/svc.c | 540 | ||||
| -rw-r--r-- | fs/lockd/svc4proc.c | 318 | ||||
| -rw-r--r-- | fs/lockd/svclock.c | 885 | ||||
| -rw-r--r-- | fs/lockd/svcproc.c | 339 | ||||
| -rw-r--r-- | fs/lockd/svcshare.c | 31 | ||||
| -rw-r--r-- | fs/lockd/svcsubs.c | 315 | ||||
| -rw-r--r-- | fs/lockd/xdr.c | 374 | ||||
| -rw-r--r-- | fs/lockd/xdr4.c | 330 |
17 files changed, 4351 insertions, 2398 deletions
diff --git a/fs/lockd/Makefile b/fs/lockd/Makefile index 7725a0a9a55..ca58d64374c 100644 --- a/fs/lockd/Makefile +++ b/fs/lockd/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_LOCKD) += lockd.o -lockd-objs-y := clntlock.o clntproc.o host.o svc.o svclock.o svcshare.o \ - svcproc.o svcsubs.o mon.o xdr.o -lockd-objs-$(CONFIG_LOCKD_V4) += xdr4.o svc4proc.o +lockd-objs-y := clntlock.o clntproc.o clntxdr.o host.o svc.o svclock.o \ + svcshare.o svcproc.o svcsubs.o mon.o xdr.o grace.o +lockd-objs-$(CONFIG_LOCKD_V4) += clnt4xdr.o xdr4.o svc4proc.o lockd-objs := $(lockd-objs-y) diff --git a/fs/lockd/clnt4xdr.c b/fs/lockd/clnt4xdr.c new file mode 100644 index 00000000000..d3e40db2893 --- /dev/null +++ b/fs/lockd/clnt4xdr.c @@ -0,0 +1,599 @@ +/* + * linux/fs/lockd/clnt4xdr.c + * + * XDR functions to encode/decode NLM version 4 RPC arguments and results. + * + * NLM client-side only. + * + * Copyright (C) 2010, Oracle. All rights reserved. + */ + +#include <linux/types.h> +#include <linux/sunrpc/xdr.h> +#include <linux/sunrpc/clnt.h> +#include <linux/sunrpc/stats.h> +#include <linux/lockd/lockd.h> + +#include <uapi/linux/nfs3.h> + +#define NLMDBG_FACILITY NLMDBG_XDR + +#if (NLMCLNT_OHSIZE > XDR_MAX_NETOBJ) +# error "NLM host name cannot be larger than XDR_MAX_NETOBJ!" +#endif + +#if (NLMCLNT_OHSIZE > NLM_MAXSTRLEN) +# error "NLM host name cannot be larger than NLM's maximum string length!" +#endif + +/* + * Declare the space requirements for NLM arguments and replies as + * number of 32bit-words + */ +#define NLM4_void_sz (0) +#define NLM4_cookie_sz (1+(NLM_MAXCOOKIELEN>>2)) +#define NLM4_caller_sz (1+(NLMCLNT_OHSIZE>>2)) +#define NLM4_owner_sz (1+(NLMCLNT_OHSIZE>>2)) +#define NLM4_fhandle_sz (1+(NFS3_FHSIZE>>2)) +#define NLM4_lock_sz (5+NLM4_caller_sz+NLM4_owner_sz+NLM4_fhandle_sz) +#define NLM4_holder_sz (6+NLM4_owner_sz) + +#define NLM4_testargs_sz (NLM4_cookie_sz+1+NLM4_lock_sz) +#define NLM4_lockargs_sz (NLM4_cookie_sz+4+NLM4_lock_sz) +#define NLM4_cancargs_sz (NLM4_cookie_sz+2+NLM4_lock_sz) +#define NLM4_unlockargs_sz (NLM4_cookie_sz+NLM4_lock_sz) + +#define NLM4_testres_sz (NLM4_cookie_sz+1+NLM4_holder_sz) +#define NLM4_res_sz (NLM4_cookie_sz+1) +#define NLM4_norep_sz (0) + + +static s64 loff_t_to_s64(loff_t offset) +{ + s64 res; + + if (offset >= NLM4_OFFSET_MAX) + res = NLM4_OFFSET_MAX; + else if (offset <= -NLM4_OFFSET_MAX) + res = -NLM4_OFFSET_MAX; + else + res = offset; + return res; +} + +static void nlm4_compute_offsets(const struct nlm_lock *lock, + u64 *l_offset, u64 *l_len) +{ + const struct file_lock *fl = &lock->fl; + + *l_offset = loff_t_to_s64(fl->fl_start); + if (fl->fl_end == OFFSET_MAX) + *l_len = 0; + else + *l_len = loff_t_to_s64(fl->fl_end - fl->fl_start + 1); +} + +/* + * Handle decode buffer overflows out-of-line. + */ +static void print_overflow_msg(const char *func, const struct xdr_stream *xdr) +{ + dprintk("lockd: %s prematurely hit the end of our receive buffer. " + "Remaining buffer length is %tu words.\n", + func, xdr->end - xdr->p); +} + + +/* + * Encode/decode NLMv4 basic data types + * + * Basic NLMv4 data types are defined in Appendix II, section 6.1.4 + * of RFC 1813: "NFS Version 3 Protocol Specification" and in Chapter + * 10 of X/Open's "Protocols for Interworking: XNFS, Version 3W". + * + * Not all basic data types have their own encoding and decoding + * functions. For run-time efficiency, some data types are encoded + * or decoded inline. + */ + +static void encode_bool(struct xdr_stream *xdr, const int value) +{ + __be32 *p; + + p = xdr_reserve_space(xdr, 4); + *p = value ? xdr_one : xdr_zero; +} + +static void encode_int32(struct xdr_stream *xdr, const s32 value) +{ + __be32 *p; + + p = xdr_reserve_space(xdr, 4); + *p = cpu_to_be32(value); +} + +/* + * typedef opaque netobj<MAXNETOBJ_SZ> + */ +static void encode_netobj(struct xdr_stream *xdr, + const u8 *data, const unsigned int length) +{ + __be32 *p; + + p = xdr_reserve_space(xdr, 4 + length); + xdr_encode_opaque(p, data, length); +} + +static int decode_netobj(struct xdr_stream *xdr, + struct xdr_netobj *obj) +{ + u32 length; + __be32 *p; + + p = xdr_inline_decode(xdr, 4); + if (unlikely(p == NULL)) + goto out_overflow; + length = be32_to_cpup(p++); + if (unlikely(length > XDR_MAX_NETOBJ)) + goto out_size; + obj->len = length; + obj->data = (u8 *)p; + return 0; +out_size: + dprintk("NFS: returned netobj was too long: %u\n", length); + return -EIO; +out_overflow: + print_overflow_msg(__func__, xdr); + return -EIO; +} + +/* + * netobj cookie; + */ +static void encode_cookie(struct xdr_stream *xdr, + const struct nlm_cookie *cookie) +{ + encode_netobj(xdr, (u8 *)&cookie->data, cookie->len); +} + +static int decode_cookie(struct xdr_stream *xdr, + struct nlm_cookie *cookie) +{ + u32 length; + __be32 *p; + + p = xdr_inline_decode(xdr, 4); + if (unlikely(p == NULL)) + goto out_overflow; + length = be32_to_cpup(p++); + /* apparently HPUX can return empty cookies */ + if (length == 0) + goto out_hpux; + if (length > NLM_MAXCOOKIELEN) + goto out_size; + p = xdr_inline_decode(xdr, length); + if (unlikely(p == NULL)) + goto out_overflow; + cookie->len = length; + memcpy(cookie->data, p, length); + return 0; +out_hpux: + cookie->len = 4; + memset(cookie->data, 0, 4); + return 0; +out_size: + dprintk("NFS: returned cookie was too long: %u\n", length); + return -EIO; +out_overflow: + print_overflow_msg(__func__, xdr); + return -EIO; +} + +/* + * netobj fh; + */ +static void encode_fh(struct xdr_stream *xdr, const struct nfs_fh *fh) +{ + encode_netobj(xdr, (u8 *)&fh->data, fh->size); +} + +/* + * enum nlm4_stats { + * NLM4_GRANTED = 0, + * NLM4_DENIED = 1, + * NLM4_DENIED_NOLOCKS = 2, + * NLM4_BLOCKED = 3, + * NLM4_DENIED_GRACE_PERIOD = 4, + * NLM4_DEADLCK = 5, + * NLM4_ROFS = 6, + * NLM4_STALE_FH = 7, + * NLM4_FBIG = 8, + * NLM4_FAILED = 9 + * }; + * + * struct nlm4_stat { + * nlm4_stats stat; + * }; + * + * NB: we don't swap bytes for the NLM status values. The upper + * layers deal directly with the status value in network byte + * order. + */ +static void encode_nlm4_stat(struct xdr_stream *xdr, + const __be32 stat) +{ + __be32 *p; + + BUG_ON(be32_to_cpu(stat) > NLM_FAILED); + p = xdr_reserve_space(xdr, 4); + *p = stat; +} + +static int decode_nlm4_stat(struct xdr_stream *xdr, __be32 *stat) +{ + __be32 *p; + + p = xdr_inline_decode(xdr, 4); + if (unlikely(p == NULL)) + goto out_overflow; + if (unlikely(ntohl(*p) > ntohl(nlm4_failed))) + goto out_bad_xdr; + *stat = *p; + return 0; +out_bad_xdr: + dprintk("%s: server returned invalid nlm4_stats value: %u\n", + __func__, be32_to_cpup(p)); + return -EIO; +out_overflow: + print_overflow_msg(__func__, xdr); + return -EIO; +} + +/* + * struct nlm4_holder { + * bool exclusive; + * int32 svid; + * netobj oh; + * uint64 l_offset; + * uint64 l_len; + * }; + */ +static void encode_nlm4_holder(struct xdr_stream *xdr, + const struct nlm_res *result) +{ + const struct nlm_lock *lock = &result->lock; + u64 l_offset, l_len; + __be32 *p; + + encode_bool(xdr, lock->fl.fl_type == F_RDLCK); + encode_int32(xdr, lock->svid); + encode_netobj(xdr, lock->oh.data, lock->oh.len); + + p = xdr_reserve_space(xdr, 4 + 4); + nlm4_compute_offsets(lock, &l_offset, &l_len); + p = xdr_encode_hyper(p, l_offset); + xdr_encode_hyper(p, l_len); +} + +static int decode_nlm4_holder(struct xdr_stream *xdr, struct nlm_res *result) +{ + struct nlm_lock *lock = &result->lock; + struct file_lock *fl = &lock->fl; + u64 l_offset, l_len; + u32 exclusive; + int error; + __be32 *p; + s32 end; + + memset(lock, 0, sizeof(*lock)); + locks_init_lock(fl); + + p = xdr_inline_decode(xdr, 4 + 4); + if (unlikely(p == NULL)) + goto out_overflow; + exclusive = be32_to_cpup(p++); + lock->svid = be32_to_cpup(p); + fl->fl_pid = (pid_t)lock->svid; + + error = decode_netobj(xdr, &lock->oh); + if (unlikely(error)) + goto out; + + p = xdr_inline_decode(xdr, 8 + 8); + if (unlikely(p == NULL)) + goto out_overflow; + + fl->fl_flags = FL_POSIX; + fl->fl_type = exclusive != 0 ? F_WRLCK : F_RDLCK; + p = xdr_decode_hyper(p, &l_offset); + xdr_decode_hyper(p, &l_len); + end = l_offset + l_len - 1; + + fl->fl_start = (loff_t)l_offset; + if (l_len == 0 || end < 0) + fl->fl_end = OFFSET_MAX; + else + fl->fl_end = (loff_t)end; + error = 0; +out: + return error; +out_overflow: + print_overflow_msg(__func__, xdr); + return -EIO; +} + +/* + * string caller_name<LM_MAXSTRLEN>; + */ +static void encode_caller_name(struct xdr_stream *xdr, const char *name) +{ + /* NB: client-side does not set lock->len */ + u32 length = strlen(name); + __be32 *p; + + p = xdr_reserve_space(xdr, 4 + length); + xdr_encode_opaque(p, name, length); +} + +/* + * struct nlm4_lock { + * string caller_name<LM_MAXSTRLEN>; + * netobj fh; + * netobj oh; + * int32 svid; + * uint64 l_offset; + * uint64 l_len; + * }; + */ +static void encode_nlm4_lock(struct xdr_stream *xdr, + const struct nlm_lock *lock) +{ + u64 l_offset, l_len; + __be32 *p; + + encode_caller_name(xdr, lock->caller); + encode_fh(xdr, &lock->fh); + encode_netobj(xdr, lock->oh.data, lock->oh.len); + + p = xdr_reserve_space(xdr, 4 + 8 + 8); + *p++ = cpu_to_be32(lock->svid); + + nlm4_compute_offsets(lock, &l_offset, &l_len); + p = xdr_encode_hyper(p, l_offset); + xdr_encode_hyper(p, l_len); +} + + +/* + * NLMv4 XDR encode functions + * + * NLMv4 argument types are defined in Appendix II of RFC 1813: + * "NFS Version 3 Protocol Specification" and Chapter 10 of X/Open's + * "Protocols for Interworking: XNFS, Version 3W". + */ + +/* + * struct nlm4_testargs { + * netobj cookie; + * bool exclusive; + * struct nlm4_lock alock; + * }; + */ +static void nlm4_xdr_enc_testargs(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_args *args) +{ + const struct nlm_lock *lock = &args->lock; + + encode_cookie(xdr, &args->cookie); + encode_bool(xdr, lock->fl.fl_type == F_WRLCK); + encode_nlm4_lock(xdr, lock); +} + +/* + * struct nlm4_lockargs { + * netobj cookie; + * bool block; + * bool exclusive; + * struct nlm4_lock alock; + * bool reclaim; + * int state; + * }; + */ +static void nlm4_xdr_enc_lockargs(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_args *args) +{ + const struct nlm_lock *lock = &args->lock; + + encode_cookie(xdr, &args->cookie); + encode_bool(xdr, args->block); + encode_bool(xdr, lock->fl.fl_type == F_WRLCK); + encode_nlm4_lock(xdr, lock); + encode_bool(xdr, args->reclaim); + encode_int32(xdr, args->state); +} + +/* + * struct nlm4_cancargs { + * netobj cookie; + * bool block; + * bool exclusive; + * struct nlm4_lock alock; + * }; + */ +static void nlm4_xdr_enc_cancargs(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_args *args) +{ + const struct nlm_lock *lock = &args->lock; + + encode_cookie(xdr, &args->cookie); + encode_bool(xdr, args->block); + encode_bool(xdr, lock->fl.fl_type == F_WRLCK); + encode_nlm4_lock(xdr, lock); +} + +/* + * struct nlm4_unlockargs { + * netobj cookie; + * struct nlm4_lock alock; + * }; + */ +static void nlm4_xdr_enc_unlockargs(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_args *args) +{ + const struct nlm_lock *lock = &args->lock; + + encode_cookie(xdr, &args->cookie); + encode_nlm4_lock(xdr, lock); +} + +/* + * struct nlm4_res { + * netobj cookie; + * nlm4_stat stat; + * }; + */ +static void nlm4_xdr_enc_res(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_res *result) +{ + encode_cookie(xdr, &result->cookie); + encode_nlm4_stat(xdr, result->status); +} + +/* + * union nlm4_testrply switch (nlm4_stats stat) { + * case NLM4_DENIED: + * struct nlm4_holder holder; + * default: + * void; + * }; + * + * struct nlm4_testres { + * netobj cookie; + * nlm4_testrply test_stat; + * }; + */ +static void nlm4_xdr_enc_testres(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_res *result) +{ + encode_cookie(xdr, &result->cookie); + encode_nlm4_stat(xdr, result->status); + if (result->status == nlm_lck_denied) + encode_nlm4_holder(xdr, result); +} + + +/* + * NLMv4 XDR decode functions + * + * NLMv4 argument types are defined in Appendix II of RFC 1813: + * "NFS Version 3 Protocol Specification" and Chapter 10 of X/Open's + * "Protocols for Interworking: XNFS, Version 3W". + */ + +/* + * union nlm4_testrply switch (nlm4_stats stat) { + * case NLM4_DENIED: + * struct nlm4_holder holder; + * default: + * void; + * }; + * + * struct nlm4_testres { + * netobj cookie; + * nlm4_testrply test_stat; + * }; + */ +static int decode_nlm4_testrply(struct xdr_stream *xdr, + struct nlm_res *result) +{ + int error; + + error = decode_nlm4_stat(xdr, &result->status); + if (unlikely(error)) + goto out; + if (result->status == nlm_lck_denied) + error = decode_nlm4_holder(xdr, result); +out: + return error; +} + +static int nlm4_xdr_dec_testres(struct rpc_rqst *req, + struct xdr_stream *xdr, + struct nlm_res *result) +{ + int error; + + error = decode_cookie(xdr, &result->cookie); + if (unlikely(error)) + goto out; + error = decode_nlm4_testrply(xdr, result); +out: + return error; +} + +/* + * struct nlm4_res { + * netobj cookie; + * nlm4_stat stat; + * }; + */ +static int nlm4_xdr_dec_res(struct rpc_rqst *req, + struct xdr_stream *xdr, + struct nlm_res *result) +{ + int error; + + error = decode_cookie(xdr, &result->cookie); + if (unlikely(error)) + goto out; + error = decode_nlm4_stat(xdr, &result->status); +out: + return error; +} + + +/* + * For NLM, a void procedure really returns nothing + */ +#define nlm4_xdr_dec_norep NULL + +#define PROC(proc, argtype, restype) \ +[NLMPROC_##proc] = { \ + .p_proc = NLMPROC_##proc, \ + .p_encode = (kxdreproc_t)nlm4_xdr_enc_##argtype, \ + .p_decode = (kxdrdproc_t)nlm4_xdr_dec_##restype, \ + .p_arglen = NLM4_##argtype##_sz, \ + .p_replen = NLM4_##restype##_sz, \ + .p_statidx = NLMPROC_##proc, \ + .p_name = #proc, \ + } + +static struct rpc_procinfo nlm4_procedures[] = { + PROC(TEST, testargs, testres), + PROC(LOCK, lockargs, res), + PROC(CANCEL, cancargs, res), + PROC(UNLOCK, unlockargs, res), + PROC(GRANTED, testargs, res), + PROC(TEST_MSG, testargs, norep), + PROC(LOCK_MSG, lockargs, norep), + PROC(CANCEL_MSG, cancargs, norep), + PROC(UNLOCK_MSG, unlockargs, norep), + PROC(GRANTED_MSG, testargs, norep), + PROC(TEST_RES, testres, norep), + PROC(LOCK_RES, res, norep), + PROC(CANCEL_RES, res, norep), + PROC(UNLOCK_RES, res, norep), + PROC(GRANTED_RES, res, norep), +}; + +const struct rpc_version nlm_version4 = { + .number = 4, + .nrprocs = ARRAY_SIZE(nlm4_procedures), + .procs = nlm4_procedures, +}; diff --git a/fs/lockd/clntlock.c b/fs/lockd/clntlock.c index 3eaf6e70108..41e491b8e5d 100644 --- a/fs/lockd/clntlock.c +++ b/fs/lockd/clntlock.c @@ -8,12 +8,13 @@ #include <linux/module.h> #include <linux/types.h> +#include <linux/slab.h> #include <linux/time.h> #include <linux/nfs_fs.h> -#include <linux/sunrpc/clnt.h> +#include <linux/sunrpc/addr.h> #include <linux/sunrpc/svc.h> #include <linux/lockd/lockd.h> -#include <linux/smp_lock.h> +#include <linux/kthread.h> #define NLMDBG_FACILITY NLMDBG_CLIENT @@ -36,56 +37,103 @@ struct nlm_wait { struct nlm_host * b_host; struct file_lock * b_lock; /* local file lock */ unsigned short b_reclaim; /* got to reclaim lock */ - u32 b_status; /* grant callback status */ + __be32 b_status; /* grant callback status */ }; static LIST_HEAD(nlm_blocked); +static DEFINE_SPINLOCK(nlm_blocked_lock); + +/** + * nlmclnt_init - Set up per-NFS mount point lockd data structures + * @nlm_init: pointer to arguments structure + * + * Returns pointer to an appropriate nlm_host struct, + * or an ERR_PTR value. + */ +struct nlm_host *nlmclnt_init(const struct nlmclnt_initdata *nlm_init) +{ + struct nlm_host *host; + u32 nlm_version = (nlm_init->nfs_version == 2) ? 1 : 4; + int status; + + status = lockd_up(nlm_init->net); + if (status < 0) + return ERR_PTR(status); + + host = nlmclnt_lookup_host(nlm_init->address, nlm_init->addrlen, + nlm_init->protocol, nlm_version, + nlm_init->hostname, nlm_init->noresvport, + nlm_init->net); + if (host == NULL) + goto out_nohost; + if (host->h_rpcclnt == NULL && nlm_bind_host(host) == NULL) + goto out_nobind; + + return host; +out_nobind: + nlmclnt_release_host(host); +out_nohost: + lockd_down(nlm_init->net); + return ERR_PTR(-ENOLCK); +} +EXPORT_SYMBOL_GPL(nlmclnt_init); + +/** + * nlmclnt_done - Release resources allocated by nlmclnt_init() + * @host: nlm_host structure reserved by nlmclnt_init() + * + */ +void nlmclnt_done(struct nlm_host *host) +{ + struct net *net = host->net; + + nlmclnt_release_host(host); + lockd_down(net); +} +EXPORT_SYMBOL_GPL(nlmclnt_done); /* * Queue up a lock for blocking so that the GRANTED request can see it */ -int nlmclnt_prepare_block(struct nlm_rqst *req, struct nlm_host *host, struct file_lock *fl) +struct nlm_wait *nlmclnt_prepare_block(struct nlm_host *host, struct file_lock *fl) { struct nlm_wait *block; - BUG_ON(req->a_block != NULL); block = kmalloc(sizeof(*block), GFP_KERNEL); - if (block == NULL) - return -ENOMEM; - block->b_host = host; - block->b_lock = fl; - init_waitqueue_head(&block->b_wait); - block->b_status = NLM_LCK_BLOCKED; - - list_add(&block->b_list, &nlm_blocked); - req->a_block = block; - - return 0; + if (block != NULL) { + block->b_host = host; + block->b_lock = fl; + init_waitqueue_head(&block->b_wait); + block->b_status = nlm_lck_blocked; + + spin_lock(&nlm_blocked_lock); + list_add(&block->b_list, &nlm_blocked); + spin_unlock(&nlm_blocked_lock); + } + return block; } -void nlmclnt_finish_block(struct nlm_rqst *req) +void nlmclnt_finish_block(struct nlm_wait *block) { - struct nlm_wait *block = req->a_block; - if (block == NULL) return; - req->a_block = NULL; + spin_lock(&nlm_blocked_lock); list_del(&block->b_list); + spin_unlock(&nlm_blocked_lock); kfree(block); } /* * Block on a lock */ -long nlmclnt_block(struct nlm_rqst *req, long timeout) +int nlmclnt_block(struct nlm_wait *block, struct nlm_rqst *req, long timeout) { - struct nlm_wait *block = req->a_block; long ret; /* A borken server might ask us to block even if we didn't * request it. Just say no! */ - if (!req->a_args.block) + if (block == NULL) return -EAGAIN; /* Go to sleep waiting for GRANT callback. Some servers seem @@ -97,109 +145,81 @@ long nlmclnt_block(struct nlm_rqst *req, long timeout) * nlmclnt_lock for an explanation. */ ret = wait_event_interruptible_timeout(block->b_wait, - block->b_status != NLM_LCK_BLOCKED, + block->b_status != nlm_lck_blocked, timeout); - - if (block->b_status != NLM_LCK_BLOCKED) { - req->a_res.status = block->b_status; - block->b_status = NLM_LCK_BLOCKED; - } - - return ret; + if (ret < 0) + return -ERESTARTSYS; + /* Reset the lock status after a server reboot so we resend */ + if (block->b_status == nlm_lck_denied_grace_period) + block->b_status = nlm_lck_blocked; + req->a_res.status = block->b_status; + return 0; } /* * The server lockd has called us back to tell us the lock was granted */ -u32 -nlmclnt_grant(struct nlm_lock *lock) +__be32 nlmclnt_grant(const struct sockaddr *addr, const struct nlm_lock *lock) { + const struct file_lock *fl = &lock->fl; + const struct nfs_fh *fh = &lock->fh; struct nlm_wait *block; - u32 res = nlm_lck_denied; + __be32 res = nlm_lck_denied; /* * Look up blocked request based on arguments. * Warning: must not use cookie to match it! */ + spin_lock(&nlm_blocked_lock); list_for_each_entry(block, &nlm_blocked, b_list) { - if (nlm_compare_locks(block->b_lock, &lock->fl)) { - /* Alright, we found a lock. Set the return status - * and wake up the caller - */ - block->b_status = NLM_LCK_GRANTED; - wake_up(&block->b_wait); - res = nlm_granted; - } - } - return res; -} + struct file_lock *fl_blocked = block->b_lock; -/* - * The following procedures deal with the recovery of locks after a - * server crash. - */ - -/* - * Mark the locks for reclaiming. - * FIXME: In 2.5 we don't want to iterate through any global file_lock_list. - * Maintain NLM lock reclaiming lists in the nlm_host instead. - */ -static -void nlmclnt_mark_reclaim(struct nlm_host *host) -{ - struct file_lock *fl; - struct inode *inode; - struct list_head *tmp; - - list_for_each(tmp, &file_lock_list) { - fl = list_entry(tmp, struct file_lock, fl_link); - - inode = fl->fl_file->f_dentry->d_inode; - if (inode->i_sb->s_magic != NFS_SUPER_MAGIC) + if (fl_blocked->fl_start != fl->fl_start) + continue; + if (fl_blocked->fl_end != fl->fl_end) continue; - if (fl->fl_u.nfs_fl.owner == NULL) + /* + * Careful! The NLM server will return the 32-bit "pid" that + * we put on the wire: in this case the lockowner "pid". + */ + if (fl_blocked->fl_u.nfs_fl.owner->pid != lock->svid) continue; - if (fl->fl_u.nfs_fl.owner->host != host) + if (!rpc_cmp_addr(nlm_addr(block->b_host), addr)) continue; - if (!(fl->fl_u.nfs_fl.flags & NFS_LCK_GRANTED)) + if (nfs_compare_fh(NFS_FH(file_inode(fl_blocked->fl_file)) ,fh) != 0) continue; - fl->fl_u.nfs_fl.flags |= NFS_LCK_RECLAIM; + /* Alright, we found a lock. Set the return status + * and wake up the caller + */ + block->b_status = nlm_granted; + wake_up(&block->b_wait); + res = nlm_granted; } + spin_unlock(&nlm_blocked_lock); + return res; } /* - * Someone has sent us an SM_NOTIFY. Ensure we bind to the new port number, - * that we mark locks for reclaiming, and that we bump the pseudo NSM state. + * The following procedures deal with the recovery of locks after a + * server crash. */ -static inline -void nlmclnt_prepare_reclaim(struct nlm_host *host, u32 newstate) -{ - host->h_monitored = 0; - host->h_nsmstate = newstate; - host->h_state++; - host->h_nextrebind = 0; - nlm_rebind_host(host); - nlmclnt_mark_reclaim(host); - dprintk("NLM: reclaiming locks for host %s", host->h_name); -} /* * Reclaim all locks on server host. We do this by spawning a separate * reclaimer thread. */ void -nlmclnt_recovery(struct nlm_host *host, u32 newstate) +nlmclnt_recovery(struct nlm_host *host) { - if (host->h_reclaiming++) { - if (host->h_nsmstate == newstate) - return; - nlmclnt_prepare_reclaim(host, newstate); - } else { - nlmclnt_prepare_reclaim(host, newstate); + struct task_struct *task; + + if (!host->h_reclaiming++) { nlm_get_host(host); - __module_get(THIS_MODULE); - if (kernel_thread(reclaimer, host, CLONE_KERNEL) < 0) - module_put(THIS_MODULE); + task = kthread_run(reclaimer, host, "%s-reclaim", host->h_name); + if (IS_ERR(task)) + printk(KERN_ERR "lockd: unable to spawn reclaimer " + "thread. Locks for %s won't be reclaimed! " + "(%ld)\n", host->h_name, PTR_ERR(task)); } } @@ -208,53 +228,74 @@ reclaimer(void *ptr) { struct nlm_host *host = (struct nlm_host *) ptr; struct nlm_wait *block; - struct list_head *tmp; - struct file_lock *fl; - struct inode *inode; + struct nlm_rqst *req; + struct file_lock *fl, *next; + u32 nsmstate; + struct net *net = host->net; + + req = kmalloc(sizeof(*req), GFP_KERNEL); + if (!req) { + printk(KERN_ERR "lockd: reclaimer unable to alloc memory." + " Locks for %s won't be reclaimed!\n", + host->h_name); + return 0; + } - daemonize("%s-reclaim", host->h_name); allow_signal(SIGKILL); - /* This one ensures that our parent doesn't terminate while the - * reclaim is in progress */ - lock_kernel(); - lockd_up(); + down_write(&host->h_rwsem); + lockd_up(net); /* note: this cannot fail as lockd is already running */ + + dprintk("lockd: reclaiming locks for host %s\n", host->h_name); - /* First, reclaim all locks that have been marked. */ restart: - list_for_each(tmp, &file_lock_list) { - fl = list_entry(tmp, struct file_lock, fl_link); + nsmstate = host->h_nsmstate; - inode = fl->fl_file->f_dentry->d_inode; - if (inode->i_sb->s_magic != NFS_SUPER_MAGIC) - continue; - if (fl->fl_u.nfs_fl.owner == NULL) - continue; - if (fl->fl_u.nfs_fl.owner->host != host) - continue; - if (!(fl->fl_u.nfs_fl.flags & NFS_LCK_RECLAIM)) - continue; + /* Force a portmap getport - the peer's lockd will + * most likely end up on a different port. + */ + host->h_nextrebind = jiffies; + nlm_rebind_host(host); - fl->fl_u.nfs_fl.flags &= ~NFS_LCK_RECLAIM; - nlmclnt_reclaim(host, fl); + /* First, reclaim all locks that have been granted. */ + list_splice_init(&host->h_granted, &host->h_reclaim); + list_for_each_entry_safe(fl, next, &host->h_reclaim, fl_u.nfs_fl.list) { + list_del_init(&fl->fl_u.nfs_fl.list); + + /* + * sending this thread a SIGKILL will result in any unreclaimed + * locks being removed from the h_granted list. This means that + * the kernel will not attempt to reclaim them again if a new + * reclaimer thread is spawned for this host. + */ if (signalled()) - break; - goto restart; + continue; + if (nlmclnt_reclaim(host, fl, req) != 0) + continue; + list_add_tail(&fl->fl_u.nfs_fl.list, &host->h_granted); + if (host->h_nsmstate != nsmstate) { + /* Argh! The server rebooted again! */ + goto restart; + } } host->h_reclaiming = 0; + up_write(&host->h_rwsem); + dprintk("NLM: done reclaiming locks for host %s\n", host->h_name); /* Now, wake up all processes that sleep on a blocked lock */ + spin_lock(&nlm_blocked_lock); list_for_each_entry(block, &nlm_blocked, b_list) { if (block->b_host == host) { - block->b_status = NLM_LCK_DENIED_GRACE_PERIOD; + block->b_status = nlm_lck_denied_grace_period; wake_up(&block->b_wait); } } + spin_unlock(&nlm_blocked_lock); /* Release host handle after use */ - nlm_release_host(host); - lockd_down(); - unlock_kernel(); - module_put_and_exit(0); + nlmclnt_release_host(host); + lockd_down(net); + kfree(req); + return 0; } diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c index 14552403957..acd39471634 100644 --- a/fs/lockd/clntproc.c +++ b/fs/lockd/clntproc.c @@ -6,28 +6,29 @@ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> */ -#include <linux/config.h> #include <linux/module.h> +#include <linux/slab.h> #include <linux/types.h> #include <linux/errno.h> #include <linux/fs.h> #include <linux/nfs_fs.h> #include <linux/utsname.h> -#include <linux/smp_lock.h> +#include <linux/freezer.h> #include <linux/sunrpc/clnt.h> #include <linux/sunrpc/svc.h> #include <linux/lockd/lockd.h> -#include <linux/lockd/sm_inter.h> #define NLMDBG_FACILITY NLMDBG_CLIENT #define NLMCLNT_GRACE_WAIT (5*HZ) #define NLMCLNT_POLL_TIMEOUT (30*HZ) +#define NLMCLNT_MAX_RETRIES 3 static int nlmclnt_test(struct nlm_rqst *, struct file_lock *); static int nlmclnt_lock(struct nlm_rqst *, struct file_lock *); static int nlmclnt_unlock(struct nlm_rqst *, struct file_lock *); -static int nlm_stat_to_errno(u32 stat); +static int nlm_stat_to_errno(__be32 stat); static void nlmclnt_locks_init_private(struct file_lock *fl, struct nlm_host *host); +static int nlmclnt_cancel(struct nlm_host *, int , struct file_lock *); static const struct rpc_call_ops nlmclnt_unlock_ops; static const struct rpc_call_ops nlmclnt_cancel_ops; @@ -35,14 +36,14 @@ static const struct rpc_call_ops nlmclnt_cancel_ops; /* * Cookie counter for NLM requests */ -static u32 nlm_cookie = 0x1234; +static atomic_t nlm_cookie = ATOMIC_INIT(0x1234); -static inline void nlmclnt_next_cookie(struct nlm_cookie *c) +void nlmclnt_next_cookie(struct nlm_cookie *c) { - memcpy(c->data, &nlm_cookie, 4); - memset(c->data+4, 0, 4); + u32 cookie = atomic_inc_return(&nlm_cookie); + + memcpy(c->data, &cookie, 4); c->len=4; - nlm_cookie++; } static struct nlm_lockowner *nlm_get_lockowner(struct nlm_lockowner *lockowner) @@ -57,7 +58,7 @@ static void nlm_put_lockowner(struct nlm_lockowner *lockowner) return; list_del(&lockowner->list); spin_unlock(&lockowner->host->h_lock); - nlm_release_host(lockowner->host); + nlmclnt_release_host(lockowner->host); kfree(lockowner); } @@ -99,7 +100,7 @@ static struct nlm_lockowner *nlm_find_lockowner(struct nlm_host *host, fl_owner_ res = __nlm_find_lockowner(host, owner); if (res == NULL) { spin_unlock(&host->h_lock); - new = (struct nlm_lockowner *)kmalloc(sizeof(*new), GFP_KERNEL); + new = kmalloc(sizeof(*new), GFP_KERNEL); spin_lock(&host->h_lock); res = __nlm_find_lockowner(host, owner); if (res == NULL && new != NULL) { @@ -124,136 +125,48 @@ static void nlmclnt_setlockargs(struct nlm_rqst *req, struct file_lock *fl) { struct nlm_args *argp = &req->a_args; struct nlm_lock *lock = &argp->lock; + char *nodename = req->a_host->h_rpcclnt->cl_nodename; nlmclnt_next_cookie(&argp->cookie); - argp->state = nsm_local_state; - memcpy(&lock->fh, NFS_FH(fl->fl_file->f_dentry->d_inode), sizeof(struct nfs_fh)); - lock->caller = system_utsname.nodename; + memcpy(&lock->fh, NFS_FH(file_inode(fl->fl_file)), sizeof(struct nfs_fh)); + lock->caller = nodename; lock->oh.data = req->a_owner; - lock->oh.len = sprintf(req->a_owner, "%d@%s", - current->pid, system_utsname.nodename); - locks_copy_lock(&lock->fl, fl); + lock->oh.len = snprintf(req->a_owner, sizeof(req->a_owner), "%u@%s", + (unsigned int)fl->fl_u.nfs_fl.owner->pid, + nodename); + lock->svid = fl->fl_u.nfs_fl.owner->pid; + lock->fl.fl_start = fl->fl_start; + lock->fl.fl_end = fl->fl_end; + lock->fl.fl_type = fl->fl_type; } static void nlmclnt_release_lockargs(struct nlm_rqst *req) { - struct file_lock *fl = &req->a_args.lock.fl; - - if (fl->fl_ops && fl->fl_ops->fl_release_private) - fl->fl_ops->fl_release_private(fl); + WARN_ON_ONCE(req->a_args.lock.fl.fl_ops != NULL); } -/* - * Initialize arguments for GRANTED call. The nlm_rqst structure - * has been cleared already. +/** + * nlmclnt_proc - Perform a single client-side lock request + * @host: address of a valid nlm_host context representing the NLM server + * @cmd: fcntl-style file lock operation to perform + * @fl: address of arguments for the lock operation + * */ -int -nlmclnt_setgrantargs(struct nlm_rqst *call, struct nlm_lock *lock) +int nlmclnt_proc(struct nlm_host *host, int cmd, struct file_lock *fl) { - locks_copy_lock(&call->a_args.lock.fl, &lock->fl); - memcpy(&call->a_args.lock.fh, &lock->fh, sizeof(call->a_args.lock.fh)); - call->a_args.lock.caller = system_utsname.nodename; - call->a_args.lock.oh.len = lock->oh.len; - - /* set default data area */ - call->a_args.lock.oh.data = call->a_owner; - - if (lock->oh.len > NLMCLNT_OHSIZE) { - void *data = kmalloc(lock->oh.len, GFP_KERNEL); - if (!data) { - nlmclnt_freegrantargs(call); - return 0; - } - call->a_args.lock.oh.data = (u8 *) data; - } - - memcpy(call->a_args.lock.oh.data, lock->oh.data, lock->oh.len); - return 1; -} + struct nlm_rqst *call; + int status; -void -nlmclnt_freegrantargs(struct nlm_rqst *call) -{ - struct file_lock *fl = &call->a_args.lock.fl; - /* - * Check whether we allocated memory for the owner. - */ - if (call->a_args.lock.oh.data != (u8 *) call->a_owner) { - kfree(call->a_args.lock.oh.data); - } - if (fl->fl_ops && fl->fl_ops->fl_release_private) - fl->fl_ops->fl_release_private(fl); -} - -/* - * This is the main entry point for the NLM client. - */ -int -nlmclnt_proc(struct inode *inode, int cmd, struct file_lock *fl) -{ - struct nfs_server *nfssrv = NFS_SERVER(inode); - struct nlm_host *host; - struct nlm_rqst reqst, *call = &reqst; - sigset_t oldset; - unsigned long flags; - int status, proto, vers; - - vers = (NFS_PROTO(inode)->version == 3) ? 4 : 1; - if (NFS_PROTO(inode)->version > 3) { - printk(KERN_NOTICE "NFSv4 file locking not implemented!\n"); - return -ENOLCK; - } - - /* Retrieve transport protocol from NFS client */ - proto = NFS_CLIENT(inode)->cl_xprt->prot; - - if (!(host = nlmclnt_lookup_host(NFS_ADDR(inode), proto, vers))) - return -ENOLCK; - - /* Create RPC client handle if not there, and copy soft - * and intr flags from NFS client. */ - if (host->h_rpcclnt == NULL) { - struct rpc_clnt *clnt; - - /* Bind an rpc client to this host handle (does not - * perform a portmapper lookup) */ - if (!(clnt = nlm_bind_host(host))) { - status = -ENOLCK; - goto done; - } - clnt->cl_softrtry = nfssrv->client->cl_softrtry; - clnt->cl_intr = nfssrv->client->cl_intr; - } - - /* Keep the old signal mask */ - spin_lock_irqsave(¤t->sighand->siglock, flags); - oldset = current->blocked; - - /* If we're cleaning up locks because the process is exiting, - * perform the RPC call asynchronously. */ - if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) - && fl->fl_type == F_UNLCK - && (current->flags & PF_EXITING)) { - sigfillset(¤t->blocked); /* Mask all signals */ - recalc_sigpending(); - spin_unlock_irqrestore(¤t->sighand->siglock, flags); - - call = nlmclnt_alloc_call(); - if (!call) { - status = -ENOMEM; - goto out_restore; - } - call->a_flags = RPC_TASK_ASYNC; - } else { - spin_unlock_irqrestore(¤t->sighand->siglock, flags); - memset(call, 0, sizeof(*call)); - locks_init_lock(&call->a_args.lock.fl); - locks_init_lock(&call->a_res.lock.fl); - } - call->a_host = host; + call = nlm_alloc_call(host); + if (call == NULL) + return -ENOMEM; nlmclnt_locks_init_private(fl, host); - + if (!fl->fl_u.nfs_fl.owner) { + /* lockowner allocation has failed */ + nlmclnt_release_call(call); + return -ENOMEM; + } /* Set up the argument struct */ nlmclnt_setlockargs(call, fl); @@ -267,42 +180,52 @@ nlmclnt_proc(struct inode *inode, int cmd, struct file_lock *fl) status = nlmclnt_test(call, fl); else status = -EINVAL; + fl->fl_ops->fl_release_private(fl); + fl->fl_ops = NULL; - out_restore: - spin_lock_irqsave(¤t->sighand->siglock, flags); - current->blocked = oldset; - recalc_sigpending(); - spin_unlock_irqrestore(¤t->sighand->siglock, flags); - -done: dprintk("lockd: clnt proc returns %d\n", status); - nlm_release_host(host); return status; } -EXPORT_SYMBOL(nlmclnt_proc); +EXPORT_SYMBOL_GPL(nlmclnt_proc); /* * Allocate an NLM RPC call struct */ -struct nlm_rqst * -nlmclnt_alloc_call(void) +struct nlm_rqst *nlm_alloc_call(struct nlm_host *host) { struct nlm_rqst *call; - while (!signalled()) { - call = (struct nlm_rqst *) kmalloc(sizeof(struct nlm_rqst), GFP_KERNEL); - if (call) { - memset(call, 0, sizeof(*call)); + for(;;) { + call = kzalloc(sizeof(*call), GFP_KERNEL); + if (call != NULL) { + atomic_set(&call->a_count, 1); locks_init_lock(&call->a_args.lock.fl); locks_init_lock(&call->a_res.lock.fl); + call->a_host = nlm_get_host(host); return call; } - printk("nlmclnt_alloc_call: failed, waiting for memory\n"); + if (signalled()) + break; + printk("nlm_alloc_call: failed, waiting for memory\n"); schedule_timeout_interruptible(5*HZ); } return NULL; } +void nlmclnt_release_call(struct nlm_rqst *call) +{ + if (!atomic_dec_and_test(&call->a_count)) + return; + nlmclnt_release_host(call->a_host); + nlmclnt_release_lockargs(call); + kfree(call); +} + +static void nlmclnt_rpc_release(void *data) +{ + nlmclnt_release_call(data); +} + static int nlm_wait_on_grace(wait_queue_head_t *queue) { DEFINE_WAIT(wait); @@ -323,7 +246,7 @@ static int nlm_wait_on_grace(wait_queue_head_t *queue) * Generic NLM call */ static int -nlmclnt_call(struct nlm_rqst *req, u32 proc) +nlmclnt_call(struct rpc_cred *cred, struct nlm_rqst *req, u32 proc) { struct nlm_host *host = req->a_host; struct rpc_clnt *clnt; @@ -332,6 +255,7 @@ nlmclnt_call(struct nlm_rqst *req, u32 proc) struct rpc_message msg = { .rpc_argp = argp, .rpc_resp = resp, + .rpc_cred = cred, }; int status; @@ -367,7 +291,7 @@ nlmclnt_call(struct nlm_rqst *req, u32 proc) } break; } else - if (resp->status == NLM_LCK_DENIED_GRACE_PERIOD) { + if (resp->status == nlm_lck_denied_grace_period) { dprintk("lockd: server in grace period\n"); if (argp->reclaim) { printk(KERN_WARNING @@ -379,7 +303,8 @@ nlmclnt_call(struct nlm_rqst *req, u32 proc) /* We appear to be out of the grace period */ wake_up_all(&host->h_gracewait); } - dprintk("lockd: server returns status %d\n", resp->status); + dprintk("lockd: server returns status %d\n", + ntohl(resp->status)); return 0; /* Okay, call complete */ } @@ -399,57 +324,89 @@ in_grace_period: /* * Generic NLM call, async version. */ -int nlmsvc_async_call(struct nlm_rqst *req, u32 proc, const struct rpc_call_ops *tk_ops) +static struct rpc_task *__nlm_async_call(struct nlm_rqst *req, u32 proc, struct rpc_message *msg, const struct rpc_call_ops *tk_ops) { struct nlm_host *host = req->a_host; struct rpc_clnt *clnt; - struct rpc_message msg = { - .rpc_argp = &req->a_args, - .rpc_resp = &req->a_res, + struct rpc_task_setup task_setup_data = { + .rpc_message = msg, + .callback_ops = tk_ops, + .callback_data = req, + .flags = RPC_TASK_ASYNC, }; - int status; dprintk("lockd: call procedure %d on %s (async)\n", (int)proc, host->h_name); /* If we have no RPC client yet, create one. */ - if ((clnt = nlm_bind_host(host)) == NULL) - return -ENOLCK; - msg.rpc_proc = &clnt->cl_procinfo[proc]; + clnt = nlm_bind_host(host); + if (clnt == NULL) + goto out_err; + msg->rpc_proc = &clnt->cl_procinfo[proc]; + task_setup_data.rpc_client = clnt; /* bootstrap and kick off the async RPC call */ - status = rpc_call_async(clnt, &msg, RPC_TASK_ASYNC, tk_ops, req); + return rpc_run_task(&task_setup_data); +out_err: + tk_ops->rpc_release(req); + return ERR_PTR(-ENOLCK); +} - return status; +static int nlm_do_async_call(struct nlm_rqst *req, u32 proc, struct rpc_message *msg, const struct rpc_call_ops *tk_ops) +{ + struct rpc_task *task; + + task = __nlm_async_call(req, proc, msg, tk_ops); + if (IS_ERR(task)) + return PTR_ERR(task); + rpc_put_task(task); + return 0; } -static int nlmclnt_async_call(struct nlm_rqst *req, u32 proc, const struct rpc_call_ops *tk_ops) +/* + * NLM asynchronous call. + */ +int nlm_async_call(struct nlm_rqst *req, u32 proc, const struct rpc_call_ops *tk_ops) { - struct nlm_host *host = req->a_host; - struct rpc_clnt *clnt; - struct nlm_args *argp = &req->a_args; - struct nlm_res *resp = &req->a_res; struct rpc_message msg = { - .rpc_argp = argp, - .rpc_resp = resp, + .rpc_argp = &req->a_args, + .rpc_resp = &req->a_res, }; - int status; - - dprintk("lockd: call procedure %d on %s (async)\n", - (int)proc, host->h_name); + return nlm_do_async_call(req, proc, &msg, tk_ops); +} - /* If we have no RPC client yet, create one. */ - if ((clnt = nlm_bind_host(host)) == NULL) - return -ENOLCK; - msg.rpc_proc = &clnt->cl_procinfo[proc]; +int nlm_async_reply(struct nlm_rqst *req, u32 proc, const struct rpc_call_ops *tk_ops) +{ + struct rpc_message msg = { + .rpc_argp = &req->a_res, + }; + return nlm_do_async_call(req, proc, &msg, tk_ops); +} - /* Increment host refcount */ - nlm_get_host(host); - /* bootstrap and kick off the async RPC call */ - status = rpc_call_async(clnt, &msg, RPC_TASK_ASYNC, tk_ops, req); - if (status < 0) - nlm_release_host(host); - return status; +/* + * NLM client asynchronous call. + * + * Note that although the calls are asynchronous, and are therefore + * guaranteed to complete, we still always attempt to wait for + * completion in order to be able to correctly track the lock + * state. + */ +static int nlmclnt_async_call(struct rpc_cred *cred, struct nlm_rqst *req, u32 proc, const struct rpc_call_ops *tk_ops) +{ + struct rpc_message msg = { + .rpc_argp = &req->a_args, + .rpc_resp = &req->a_res, + .rpc_cred = cred, + }; + struct rpc_task *task; + int err; + + task = __nlm_async_call(req, proc, &msg, tk_ops); + if (IS_ERR(task)) + return PTR_ERR(task); + err = rpc_wait_for_completion_task(task); + rpc_put_task(task); + return err; } /* @@ -460,54 +417,62 @@ nlmclnt_test(struct nlm_rqst *req, struct file_lock *fl) { int status; - status = nlmclnt_call(req, NLMPROC_TEST); - nlmclnt_release_lockargs(req); + status = nlmclnt_call(nfs_file_cred(fl->fl_file), req, NLMPROC_TEST); if (status < 0) - return status; + goto out; - status = req->a_res.status; - if (status == NLM_LCK_GRANTED) { - fl->fl_type = F_UNLCK; - } if (status == NLM_LCK_DENIED) { - /* - * Report the conflicting lock back to the application. - */ - locks_copy_lock(fl, &req->a_res.lock.fl); - fl->fl_pid = 0; - } else { - return nlm_stat_to_errno(req->a_res.status); + switch (req->a_res.status) { + case nlm_granted: + fl->fl_type = F_UNLCK; + break; + case nlm_lck_denied: + /* + * Report the conflicting lock back to the application. + */ + fl->fl_start = req->a_res.lock.fl.fl_start; + fl->fl_end = req->a_res.lock.fl.fl_end; + fl->fl_type = req->a_res.lock.fl.fl_type; + fl->fl_pid = 0; + break; + default: + status = nlm_stat_to_errno(req->a_res.status); } - - return 0; +out: + nlmclnt_release_call(req); + return status; } static void nlmclnt_locks_copy_lock(struct file_lock *new, struct file_lock *fl) { - memcpy(&new->fl_u.nfs_fl, &fl->fl_u.nfs_fl, sizeof(new->fl_u.nfs_fl)); - nlm_get_lockowner(new->fl_u.nfs_fl.owner); + spin_lock(&fl->fl_u.nfs_fl.owner->host->h_lock); + new->fl_u.nfs_fl.state = fl->fl_u.nfs_fl.state; + new->fl_u.nfs_fl.owner = nlm_get_lockowner(fl->fl_u.nfs_fl.owner); + list_add_tail(&new->fl_u.nfs_fl.list, &fl->fl_u.nfs_fl.owner->host->h_granted); + spin_unlock(&fl->fl_u.nfs_fl.owner->host->h_lock); } static void nlmclnt_locks_release_private(struct file_lock *fl) { + spin_lock(&fl->fl_u.nfs_fl.owner->host->h_lock); + list_del(&fl->fl_u.nfs_fl.list); + spin_unlock(&fl->fl_u.nfs_fl.owner->host->h_lock); nlm_put_lockowner(fl->fl_u.nfs_fl.owner); - fl->fl_ops = NULL; } -static struct file_lock_operations nlmclnt_lock_ops = { +static const struct file_lock_operations nlmclnt_lock_ops = { .fl_copy_lock = nlmclnt_locks_copy_lock, .fl_release_private = nlmclnt_locks_release_private, }; static void nlmclnt_locks_init_private(struct file_lock *fl, struct nlm_host *host) { - BUG_ON(fl->fl_ops != NULL); fl->fl_u.nfs_fl.state = 0; - fl->fl_u.nfs_fl.flags = 0; fl->fl_u.nfs_fl.owner = nlm_find_lockowner(host, fl->fl_owner); + INIT_LIST_HEAD(&fl->fl_u.nfs_fl.list); fl->fl_ops = &nlmclnt_lock_ops; } -static void do_vfs_lock(struct file_lock *fl) +static int do_vfs_lock(struct file_lock *fl) { int res = 0; switch (fl->fl_flags & (FL_POSIX|FL_FLOCK)) { @@ -520,9 +485,7 @@ static void do_vfs_lock(struct file_lock *fl) default: BUG(); } - if (res < 0) - printk(KERN_WARNING "%s: VFS is out of sync with lock manager!\n", - __FUNCTION__); + return res; } /* @@ -548,59 +511,104 @@ static void do_vfs_lock(struct file_lock *fl) static int nlmclnt_lock(struct nlm_rqst *req, struct file_lock *fl) { + struct rpc_cred *cred = nfs_file_cred(fl->fl_file); struct nlm_host *host = req->a_host; struct nlm_res *resp = &req->a_res; - long timeout; - int status; + struct nlm_wait *block = NULL; + unsigned char fl_flags = fl->fl_flags; + unsigned char fl_type; + int status = -ENOLCK; - if (!host->h_monitored && nsm_monitor(host) < 0) { - printk(KERN_NOTICE "lockd: failed to monitor %s\n", - host->h_name); - status = -ENOLCK; + if (nsm_monitor(host) < 0) goto out; - } + req->a_args.state = nsm_local_state; - if (req->a_args.block) { - status = nlmclnt_prepare_block(req, host, fl); - if (status < 0) - goto out; - } + fl->fl_flags |= FL_ACCESS; + status = do_vfs_lock(fl); + fl->fl_flags = fl_flags; + if (status < 0) + goto out; + + block = nlmclnt_prepare_block(host, fl); +again: + /* + * Initialise resp->status to a valid non-zero value, + * since 0 == nlm_lck_granted + */ + resp->status = nlm_lck_blocked; for(;;) { - status = nlmclnt_call(req, NLMPROC_LOCK); + /* Reboot protection */ + fl->fl_u.nfs_fl.state = host->h_state; + status = nlmclnt_call(cred, req, NLMPROC_LOCK); if (status < 0) - goto out_unblock; - if (resp->status != NLM_LCK_BLOCKED) break; - /* Wait on an NLM blocking lock */ - timeout = nlmclnt_block(req, NLMCLNT_POLL_TIMEOUT); /* Did a reclaimer thread notify us of a server reboot? */ - if (resp->status == NLM_LCK_DENIED_GRACE_PERIOD) + if (resp->status == nlm_lck_denied_grace_period) continue; - if (resp->status != NLM_LCK_BLOCKED) + if (resp->status != nlm_lck_blocked) + break; + /* Wait on an NLM blocking lock */ + status = nlmclnt_block(block, req, NLMCLNT_POLL_TIMEOUT); + if (status < 0) + break; + if (resp->status != nlm_lck_blocked) break; - if (timeout >= 0) - continue; - /* We were interrupted. Send a CANCEL request to the server - * and exit - */ - status = (int)timeout; - goto out_unblock; } - if (resp->status == NLM_LCK_GRANTED) { - fl->fl_u.nfs_fl.state = host->h_state; - fl->fl_u.nfs_fl.flags |= NFS_LCK_GRANTED; + /* if we were interrupted while blocking, then cancel the lock request + * and exit + */ + if (resp->status == nlm_lck_blocked) { + if (!req->a_args.block) + goto out_unlock; + if (nlmclnt_cancel(host, req->a_args.block, fl) == 0) + goto out_unblock; + } + + if (resp->status == nlm_granted) { + down_read(&host->h_rwsem); + /* Check whether or not the server has rebooted */ + if (fl->fl_u.nfs_fl.state != host->h_state) { + up_read(&host->h_rwsem); + goto again; + } + /* Ensure the resulting lock will get added to granted list */ fl->fl_flags |= FL_SLEEP; - do_vfs_lock(fl); + if (do_vfs_lock(fl) < 0) + printk(KERN_WARNING "%s: VFS is out of sync with lock manager!\n", __func__); + up_read(&host->h_rwsem); + fl->fl_flags = fl_flags; + status = 0; } - status = nlm_stat_to_errno(resp->status); + if (status < 0) + goto out_unlock; + /* + * EAGAIN doesn't make sense for sleeping locks, and in some + * cases NLM_LCK_DENIED is returned for a permanent error. So + * turn it into an ENOLCK. + */ + if (resp->status == nlm_lck_denied && (fl_flags & FL_SLEEP)) + status = -ENOLCK; + else + status = nlm_stat_to_errno(resp->status); out_unblock: - nlmclnt_finish_block(req); - /* Cancel the blocked request if it is still pending */ - if (resp->status == NLM_LCK_BLOCKED) - nlmclnt_cancel(host, fl); + nlmclnt_finish_block(block); out: - nlmclnt_release_lockargs(req); + nlmclnt_release_call(req); + return status; +out_unlock: + /* Fatal error: ensure that we remove the lock altogether */ + dprintk("lockd: lock attempt ended in fatal error.\n" + " Attempting to unlock.\n"); + nlmclnt_finish_block(block); + fl_type = fl->fl_type; + fl->fl_type = F_UNLCK; + down_read(&host->h_rwsem); + do_vfs_lock(fl); + up_read(&host->h_rwsem); + fl->fl_type = fl_type; + fl->fl_flags = fl_flags; + nlmclnt_async_call(cred, req, NLMPROC_UNLOCK, &nlmclnt_unlock_ops); return status; } @@ -608,29 +616,27 @@ out: * RECLAIM: Try to reclaim a lock */ int -nlmclnt_reclaim(struct nlm_host *host, struct file_lock *fl) +nlmclnt_reclaim(struct nlm_host *host, struct file_lock *fl, + struct nlm_rqst *req) { - struct nlm_rqst reqst, *req; int status; - req = &reqst; memset(req, 0, sizeof(*req)); locks_init_lock(&req->a_args.lock.fl); locks_init_lock(&req->a_res.lock.fl); req->a_host = host; - req->a_flags = 0; /* Set up the argument struct */ nlmclnt_setlockargs(req, fl); req->a_args.reclaim = 1; - if ((status = nlmclnt_call(req, NLMPROC_LOCK)) >= 0 - && req->a_res.status == NLM_LCK_GRANTED) + status = nlmclnt_call(nfs_file_cred(fl->fl_file), req, NLMPROC_LOCK); + if (status >= 0 && req->a_res.status == nlm_granted) return 0; printk(KERN_WARNING "lockd: failed to reclaim lock for pid %d " "(errno %d, status %d)\n", fl->fl_pid, - status, req->a_res.status); + status, ntohl(req->a_res.status)); /* * FIXME: This is a serious failure. We can @@ -653,54 +659,62 @@ nlmclnt_reclaim(struct nlm_host *host, struct file_lock *fl) static int nlmclnt_unlock(struct nlm_rqst *req, struct file_lock *fl) { + struct nlm_host *host = req->a_host; struct nlm_res *resp = &req->a_res; - int status; + int status; + unsigned char fl_flags = fl->fl_flags; - /* Clean the GRANTED flag now so the lock doesn't get - * reclaimed while we're stuck in the unlock call. */ - fl->fl_u.nfs_fl.flags &= ~NFS_LCK_GRANTED; - - if (req->a_flags & RPC_TASK_ASYNC) { - status = nlmclnt_async_call(req, NLMPROC_UNLOCK, - &nlmclnt_unlock_ops); - /* Hrmf... Do the unlock early since locks_remove_posix() - * really expects us to free the lock synchronously */ - do_vfs_lock(fl); - if (status < 0) { - nlmclnt_release_lockargs(req); - kfree(req); - } - return status; + /* + * Note: the server is supposed to either grant us the unlock + * request, or to deny it with NLM_LCK_DENIED_GRACE_PERIOD. In either + * case, we want to unlock. + */ + fl->fl_flags |= FL_EXISTS; + down_read(&host->h_rwsem); + status = do_vfs_lock(fl); + up_read(&host->h_rwsem); + fl->fl_flags = fl_flags; + if (status == -ENOENT) { + status = 0; + goto out; } - status = nlmclnt_call(req, NLMPROC_UNLOCK); - nlmclnt_release_lockargs(req); + atomic_inc(&req->a_count); + status = nlmclnt_async_call(nfs_file_cred(fl->fl_file), req, + NLMPROC_UNLOCK, &nlmclnt_unlock_ops); if (status < 0) - return status; - - do_vfs_lock(fl); - if (resp->status == NLM_LCK_GRANTED) - return 0; + goto out; - if (resp->status != NLM_LCK_DENIED_NOLOCKS) - printk("lockd: unexpected unlock status: %d\n", resp->status); + if (resp->status == nlm_granted) + goto out; + if (resp->status != nlm_lck_denied_nolocks) + printk("lockd: unexpected unlock status: %d\n", + ntohl(resp->status)); /* What to do now? I'm out of my depth... */ - - return -ENOLCK; + status = -ENOLCK; +out: + nlmclnt_release_call(req); + return status; } static void nlmclnt_unlock_callback(struct rpc_task *task, void *data) { struct nlm_rqst *req = data; - int status = req->a_res.status; + u32 status = ntohl(req->a_res.status); if (RPC_ASSASSINATED(task)) goto die; if (task->tk_status < 0) { dprintk("lockd: unlock failed (err = %d)\n", -task->tk_status); - goto retry_rebind; + switch (task->tk_status) { + case -EACCES: + case -EIO: + goto die; + default: + goto retry_rebind; + } } if (status == NLM_LCK_DENIED_GRACE_PERIOD) { rpc_delay(task, NLMCLNT_GRACE_WAIT); @@ -709,9 +723,6 @@ static void nlmclnt_unlock_callback(struct rpc_task *task, void *data) if (status != NLM_LCK_GRANTED) printk(KERN_WARNING "lockd: unexpected unlock status: %d\n", status); die: - nlm_release_host(req->a_host); - nlmclnt_release_lockargs(req); - kfree(req); return; retry_rebind: nlm_rebind_host(req->a_host); @@ -721,6 +732,7 @@ die: static const struct rpc_call_ops nlmclnt_unlock_ops = { .rpc_call_done = nlmclnt_unlock_callback, + .rpc_release = nlmclnt_rpc_release, }; /* @@ -728,46 +740,35 @@ static const struct rpc_call_ops nlmclnt_unlock_ops = { * We always use an async RPC call for this in order not to hang a * process that has been Ctrl-C'ed. */ -int -nlmclnt_cancel(struct nlm_host *host, struct file_lock *fl) +static int nlmclnt_cancel(struct nlm_host *host, int block, struct file_lock *fl) { struct nlm_rqst *req; - unsigned long flags; - sigset_t oldset; - int status; + int status; - /* Block all signals while setting up call */ - spin_lock_irqsave(¤t->sighand->siglock, flags); - oldset = current->blocked; - sigfillset(¤t->blocked); - recalc_sigpending(); - spin_unlock_irqrestore(¤t->sighand->siglock, flags); + dprintk("lockd: blocking lock attempt was interrupted by a signal.\n" + " Attempting to cancel lock.\n"); - req = nlmclnt_alloc_call(); + req = nlm_alloc_call(host); if (!req) return -ENOMEM; - req->a_host = host; req->a_flags = RPC_TASK_ASYNC; nlmclnt_setlockargs(req, fl); + req->a_args.block = block; - status = nlmclnt_async_call(req, NLMPROC_CANCEL, &nlmclnt_cancel_ops); - if (status < 0) { - nlmclnt_release_lockargs(req); - kfree(req); - } - - spin_lock_irqsave(¤t->sighand->siglock, flags); - current->blocked = oldset; - recalc_sigpending(); - spin_unlock_irqrestore(¤t->sighand->siglock, flags); - + atomic_inc(&req->a_count); + status = nlmclnt_async_call(nfs_file_cred(fl->fl_file), req, + NLMPROC_CANCEL, &nlmclnt_cancel_ops); + if (status == 0 && req->a_res.status == nlm_lck_denied) + status = -ENOLCK; + nlmclnt_release_call(req); return status; } static void nlmclnt_cancel_callback(struct rpc_task *task, void *data) { struct nlm_rqst *req = data; + u32 status = ntohl(req->a_res.status); if (RPC_ASSASSINATED(task)) goto die; @@ -778,12 +779,13 @@ static void nlmclnt_cancel_callback(struct rpc_task *task, void *data) goto retry_cancel; } - dprintk("lockd: cancel status %d (task %d)\n", - req->a_res.status, task->tk_pid); + dprintk("lockd: cancel status %u (task %u)\n", + status, task->tk_pid); - switch (req->a_res.status) { + switch (status) { case NLM_LCK_GRANTED: case NLM_LCK_DENIED_GRACE_PERIOD: + case NLM_LCK_DENIED: /* Everything's good */ break; case NLM_LCK_DENIED_NOLOCKS: @@ -791,16 +793,16 @@ static void nlmclnt_cancel_callback(struct rpc_task *task, void *data) goto retry_cancel; default: printk(KERN_NOTICE "lockd: weird return %d for CANCEL call\n", - req->a_res.status); + status); } die: - nlm_release_host(req->a_host); - nlmclnt_release_lockargs(req); - kfree(req); return; retry_cancel: + /* Don't ever retry more than 3 times */ + if (req->a_retries++ >= NLMCLNT_MAX_RETRIES) + goto die; nlm_rebind_host(req->a_host); rpc_restart_call(task); rpc_delay(task, 30 * HZ); @@ -808,15 +810,16 @@ retry_cancel: static const struct rpc_call_ops nlmclnt_cancel_ops = { .rpc_call_done = nlmclnt_cancel_callback, + .rpc_release = nlmclnt_rpc_release, }; /* * Convert an NLM status code to a generic kernel errno */ static int -nlm_stat_to_errno(u32 status) +nlm_stat_to_errno(__be32 status) { - switch(status) { + switch(ntohl(status)) { case NLM_LCK_GRANTED: return 0; case NLM_LCK_DENIED: @@ -840,6 +843,7 @@ nlm_stat_to_errno(u32 status) return -ENOLCK; #endif } - printk(KERN_NOTICE "lockd: unexpected server status %d\n", status); + printk(KERN_NOTICE "lockd: unexpected server status %d\n", + ntohl(status)); return -ENOLCK; } diff --git a/fs/lockd/clntxdr.c b/fs/lockd/clntxdr.c new file mode 100644 index 00000000000..3e9f7874b97 --- /dev/null +++ b/fs/lockd/clntxdr.c @@ -0,0 +1,621 @@ +/* + * linux/fs/lockd/clntxdr.c + * + * XDR functions to encode/decode NLM version 3 RPC arguments and results. + * NLM version 3 is backwards compatible with NLM versions 1 and 2. + * + * NLM client-side only. + * + * Copyright (C) 2010, Oracle. All rights reserved. + */ + +#include <linux/types.h> +#include <linux/sunrpc/xdr.h> +#include <linux/sunrpc/clnt.h> +#include <linux/sunrpc/stats.h> +#include <linux/lockd/lockd.h> + +#include <uapi/linux/nfs2.h> + +#define NLMDBG_FACILITY NLMDBG_XDR + +#if (NLMCLNT_OHSIZE > XDR_MAX_NETOBJ) +# error "NLM host name cannot be larger than XDR_MAX_NETOBJ!" +#endif + +/* + * Declare the space requirements for NLM arguments and replies as + * number of 32bit-words + */ +#define NLM_cookie_sz (1+(NLM_MAXCOOKIELEN>>2)) +#define NLM_caller_sz (1+(NLMCLNT_OHSIZE>>2)) +#define NLM_owner_sz (1+(NLMCLNT_OHSIZE>>2)) +#define NLM_fhandle_sz (1+(NFS2_FHSIZE>>2)) +#define NLM_lock_sz (3+NLM_caller_sz+NLM_owner_sz+NLM_fhandle_sz) +#define NLM_holder_sz (4+NLM_owner_sz) + +#define NLM_testargs_sz (NLM_cookie_sz+1+NLM_lock_sz) +#define NLM_lockargs_sz (NLM_cookie_sz+4+NLM_lock_sz) +#define NLM_cancargs_sz (NLM_cookie_sz+2+NLM_lock_sz) +#define NLM_unlockargs_sz (NLM_cookie_sz+NLM_lock_sz) + +#define NLM_testres_sz (NLM_cookie_sz+1+NLM_holder_sz) +#define NLM_res_sz (NLM_cookie_sz+1) +#define NLM_norep_sz (0) + + +static s32 loff_t_to_s32(loff_t offset) +{ + s32 res; + + if (offset >= NLM_OFFSET_MAX) + res = NLM_OFFSET_MAX; + else if (offset <= -NLM_OFFSET_MAX) + res = -NLM_OFFSET_MAX; + else + res = offset; + return res; +} + +static void nlm_compute_offsets(const struct nlm_lock *lock, + u32 *l_offset, u32 *l_len) +{ + const struct file_lock *fl = &lock->fl; + + *l_offset = loff_t_to_s32(fl->fl_start); + if (fl->fl_end == OFFSET_MAX) + *l_len = 0; + else + *l_len = loff_t_to_s32(fl->fl_end - fl->fl_start + 1); +} + +/* + * Handle decode buffer overflows out-of-line. + */ +static void print_overflow_msg(const char *func, const struct xdr_stream *xdr) +{ + dprintk("lockd: %s prematurely hit the end of our receive buffer. " + "Remaining buffer length is %tu words.\n", + func, xdr->end - xdr->p); +} + + +/* + * Encode/decode NLMv3 basic data types + * + * Basic NLMv3 data types are not defined in an IETF standards + * document. X/Open has a description of these data types that + * is useful. See Chapter 10 of "Protocols for Interworking: + * XNFS, Version 3W". + * + * Not all basic data types have their own encoding and decoding + * functions. For run-time efficiency, some data types are encoded + * or decoded inline. + */ + +static void encode_bool(struct xdr_stream *xdr, const int value) +{ + __be32 *p; + + p = xdr_reserve_space(xdr, 4); + *p = value ? xdr_one : xdr_zero; +} + +static void encode_int32(struct xdr_stream *xdr, const s32 value) +{ + __be32 *p; + + p = xdr_reserve_space(xdr, 4); + *p = cpu_to_be32(value); +} + +/* + * typedef opaque netobj<MAXNETOBJ_SZ> + */ +static void encode_netobj(struct xdr_stream *xdr, + const u8 *data, const unsigned int length) +{ + __be32 *p; + + p = xdr_reserve_space(xdr, 4 + length); + xdr_encode_opaque(p, data, length); +} + +static int decode_netobj(struct xdr_stream *xdr, + struct xdr_netobj *obj) +{ + u32 length; + __be32 *p; + + p = xdr_inline_decode(xdr, 4); + if (unlikely(p == NULL)) + goto out_overflow; + length = be32_to_cpup(p++); + if (unlikely(length > XDR_MAX_NETOBJ)) + goto out_size; + obj->len = length; + obj->data = (u8 *)p; + return 0; +out_size: + dprintk("NFS: returned netobj was too long: %u\n", length); + return -EIO; +out_overflow: + print_overflow_msg(__func__, xdr); + return -EIO; +} + +/* + * netobj cookie; + */ +static void encode_cookie(struct xdr_stream *xdr, + const struct nlm_cookie *cookie) +{ + encode_netobj(xdr, (u8 *)&cookie->data, cookie->len); +} + +static int decode_cookie(struct xdr_stream *xdr, + struct nlm_cookie *cookie) +{ + u32 length; + __be32 *p; + + p = xdr_inline_decode(xdr, 4); + if (unlikely(p == NULL)) + goto out_overflow; + length = be32_to_cpup(p++); + /* apparently HPUX can return empty cookies */ + if (length == 0) + goto out_hpux; + if (length > NLM_MAXCOOKIELEN) + goto out_size; + p = xdr_inline_decode(xdr, length); + if (unlikely(p == NULL)) + goto out_overflow; + cookie->len = length; + memcpy(cookie->data, p, length); + return 0; +out_hpux: + cookie->len = 4; + memset(cookie->data, 0, 4); + return 0; +out_size: + dprintk("NFS: returned cookie was too long: %u\n", length); + return -EIO; +out_overflow: + print_overflow_msg(__func__, xdr); + return -EIO; +} + +/* + * netobj fh; + */ +static void encode_fh(struct xdr_stream *xdr, const struct nfs_fh *fh) +{ + encode_netobj(xdr, (u8 *)&fh->data, NFS2_FHSIZE); +} + +/* + * enum nlm_stats { + * LCK_GRANTED = 0, + * LCK_DENIED = 1, + * LCK_DENIED_NOLOCKS = 2, + * LCK_BLOCKED = 3, + * LCK_DENIED_GRACE_PERIOD = 4 + * }; + * + * + * struct nlm_stat { + * nlm_stats stat; + * }; + * + * NB: we don't swap bytes for the NLM status values. The upper + * layers deal directly with the status value in network byte + * order. + */ + +static void encode_nlm_stat(struct xdr_stream *xdr, + const __be32 stat) +{ + __be32 *p; + + WARN_ON_ONCE(be32_to_cpu(stat) > NLM_LCK_DENIED_GRACE_PERIOD); + p = xdr_reserve_space(xdr, 4); + *p = stat; +} + +static int decode_nlm_stat(struct xdr_stream *xdr, + __be32 *stat) +{ + __be32 *p; + + p = xdr_inline_decode(xdr, 4); + if (unlikely(p == NULL)) + goto out_overflow; + if (unlikely(ntohl(*p) > ntohl(nlm_lck_denied_grace_period))) + goto out_enum; + *stat = *p; + return 0; +out_enum: + dprintk("%s: server returned invalid nlm_stats value: %u\n", + __func__, be32_to_cpup(p)); + return -EIO; +out_overflow: + print_overflow_msg(__func__, xdr); + return -EIO; +} + +/* + * struct nlm_holder { + * bool exclusive; + * int uppid; + * netobj oh; + * unsigned l_offset; + * unsigned l_len; + * }; + */ +static void encode_nlm_holder(struct xdr_stream *xdr, + const struct nlm_res *result) +{ + const struct nlm_lock *lock = &result->lock; + u32 l_offset, l_len; + __be32 *p; + + encode_bool(xdr, lock->fl.fl_type == F_RDLCK); + encode_int32(xdr, lock->svid); + encode_netobj(xdr, lock->oh.data, lock->oh.len); + + p = xdr_reserve_space(xdr, 4 + 4); + nlm_compute_offsets(lock, &l_offset, &l_len); + *p++ = cpu_to_be32(l_offset); + *p = cpu_to_be32(l_len); +} + +static int decode_nlm_holder(struct xdr_stream *xdr, struct nlm_res *result) +{ + struct nlm_lock *lock = &result->lock; + struct file_lock *fl = &lock->fl; + u32 exclusive, l_offset, l_len; + int error; + __be32 *p; + s32 end; + + memset(lock, 0, sizeof(*lock)); + locks_init_lock(fl); + + p = xdr_inline_decode(xdr, 4 + 4); + if (unlikely(p == NULL)) + goto out_overflow; + exclusive = be32_to_cpup(p++); + lock->svid = be32_to_cpup(p); + fl->fl_pid = (pid_t)lock->svid; + + error = decode_netobj(xdr, &lock->oh); + if (unlikely(error)) + goto out; + + p = xdr_inline_decode(xdr, 4 + 4); + if (unlikely(p == NULL)) + goto out_overflow; + + fl->fl_flags = FL_POSIX; + fl->fl_type = exclusive != 0 ? F_WRLCK : F_RDLCK; + l_offset = be32_to_cpup(p++); + l_len = be32_to_cpup(p); + end = l_offset + l_len - 1; + + fl->fl_start = (loff_t)l_offset; + if (l_len == 0 || end < 0) + fl->fl_end = OFFSET_MAX; + else + fl->fl_end = (loff_t)end; + error = 0; +out: + return error; +out_overflow: + print_overflow_msg(__func__, xdr); + return -EIO; +} + +/* + * string caller_name<LM_MAXSTRLEN>; + */ +static void encode_caller_name(struct xdr_stream *xdr, const char *name) +{ + /* NB: client-side does not set lock->len */ + u32 length = strlen(name); + __be32 *p; + + p = xdr_reserve_space(xdr, 4 + length); + xdr_encode_opaque(p, name, length); +} + +/* + * struct nlm_lock { + * string caller_name<LM_MAXSTRLEN>; + * netobj fh; + * netobj oh; + * int uppid; + * unsigned l_offset; + * unsigned l_len; + * }; + */ +static void encode_nlm_lock(struct xdr_stream *xdr, + const struct nlm_lock *lock) +{ + u32 l_offset, l_len; + __be32 *p; + + encode_caller_name(xdr, lock->caller); + encode_fh(xdr, &lock->fh); + encode_netobj(xdr, lock->oh.data, lock->oh.len); + + p = xdr_reserve_space(xdr, 4 + 4 + 4); + *p++ = cpu_to_be32(lock->svid); + + nlm_compute_offsets(lock, &l_offset, &l_len); + *p++ = cpu_to_be32(l_offset); + *p = cpu_to_be32(l_len); +} + + +/* + * NLMv3 XDR encode functions + * + * NLMv3 argument types are defined in Chapter 10 of The Open Group's + * "Protocols for Interworking: XNFS, Version 3W". + */ + +/* + * struct nlm_testargs { + * netobj cookie; + * bool exclusive; + * struct nlm_lock alock; + * }; + */ +static void nlm_xdr_enc_testargs(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_args *args) +{ + const struct nlm_lock *lock = &args->lock; + + encode_cookie(xdr, &args->cookie); + encode_bool(xdr, lock->fl.fl_type == F_WRLCK); + encode_nlm_lock(xdr, lock); +} + +/* + * struct nlm_lockargs { + * netobj cookie; + * bool block; + * bool exclusive; + * struct nlm_lock alock; + * bool reclaim; + * int state; + * }; + */ +static void nlm_xdr_enc_lockargs(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_args *args) +{ + const struct nlm_lock *lock = &args->lock; + + encode_cookie(xdr, &args->cookie); + encode_bool(xdr, args->block); + encode_bool(xdr, lock->fl.fl_type == F_WRLCK); + encode_nlm_lock(xdr, lock); + encode_bool(xdr, args->reclaim); + encode_int32(xdr, args->state); +} + +/* + * struct nlm_cancargs { + * netobj cookie; + * bool block; + * bool exclusive; + * struct nlm_lock alock; + * }; + */ +static void nlm_xdr_enc_cancargs(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_args *args) +{ + const struct nlm_lock *lock = &args->lock; + + encode_cookie(xdr, &args->cookie); + encode_bool(xdr, args->block); + encode_bool(xdr, lock->fl.fl_type == F_WRLCK); + encode_nlm_lock(xdr, lock); +} + +/* + * struct nlm_unlockargs { + * netobj cookie; + * struct nlm_lock alock; + * }; + */ +static void nlm_xdr_enc_unlockargs(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_args *args) +{ + const struct nlm_lock *lock = &args->lock; + + encode_cookie(xdr, &args->cookie); + encode_nlm_lock(xdr, lock); +} + +/* + * struct nlm_res { + * netobj cookie; + * nlm_stat stat; + * }; + */ +static void nlm_xdr_enc_res(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_res *result) +{ + encode_cookie(xdr, &result->cookie); + encode_nlm_stat(xdr, result->status); +} + +/* + * union nlm_testrply switch (nlm_stats stat) { + * case LCK_DENIED: + * struct nlm_holder holder; + * default: + * void; + * }; + * + * struct nlm_testres { + * netobj cookie; + * nlm_testrply test_stat; + * }; + */ +static void encode_nlm_testrply(struct xdr_stream *xdr, + const struct nlm_res *result) +{ + if (result->status == nlm_lck_denied) + encode_nlm_holder(xdr, result); +} + +static void nlm_xdr_enc_testres(struct rpc_rqst *req, + struct xdr_stream *xdr, + const struct nlm_res *result) +{ + encode_cookie(xdr, &result->cookie); + encode_nlm_stat(xdr, result->status); + encode_nlm_testrply(xdr, result); +} + + +/* + * NLMv3 XDR decode functions + * + * NLMv3 result types are defined in Chapter 10 of The Open Group's + * "Protocols for Interworking: XNFS, Version 3W". + */ + +/* + * union nlm_testrply switch (nlm_stats stat) { + * case LCK_DENIED: + * struct nlm_holder holder; + * default: + * void; + * }; + * + * struct nlm_testres { + * netobj cookie; + * nlm_testrply test_stat; + * }; + */ +static int decode_nlm_testrply(struct xdr_stream *xdr, + struct nlm_res *result) +{ + int error; + + error = decode_nlm_stat(xdr, &result->status); + if (unlikely(error)) + goto out; + if (result->status == nlm_lck_denied) + error = decode_nlm_holder(xdr, result); +out: + return error; +} + +static int nlm_xdr_dec_testres(struct rpc_rqst *req, + struct xdr_stream *xdr, + struct nlm_res *result) +{ + int error; + + error = decode_cookie(xdr, &result->cookie); + if (unlikely(error)) + goto out; + error = decode_nlm_testrply(xdr, result); +out: + return error; +} + +/* + * struct nlm_res { + * netobj cookie; + * nlm_stat stat; + * }; + */ +static int nlm_xdr_dec_res(struct rpc_rqst *req, + struct xdr_stream *xdr, + struct nlm_res *result) +{ + int error; + + error = decode_cookie(xdr, &result->cookie); + if (unlikely(error)) + goto out; + error = decode_nlm_stat(xdr, &result->status); +out: + return error; +} + + +/* + * For NLM, a void procedure really returns nothing + */ +#define nlm_xdr_dec_norep NULL + +#define PROC(proc, argtype, restype) \ +[NLMPROC_##proc] = { \ + .p_proc = NLMPROC_##proc, \ + .p_encode = (kxdreproc_t)nlm_xdr_enc_##argtype, \ + .p_decode = (kxdrdproc_t)nlm_xdr_dec_##restype, \ + .p_arglen = NLM_##argtype##_sz, \ + .p_replen = NLM_##restype##_sz, \ + .p_statidx = NLMPROC_##proc, \ + .p_name = #proc, \ + } + +static struct rpc_procinfo nlm_procedures[] = { + PROC(TEST, testargs, testres), + PROC(LOCK, lockargs, res), + PROC(CANCEL, cancargs, res), + PROC(UNLOCK, unlockargs, res), + PROC(GRANTED, testargs, res), + PROC(TEST_MSG, testargs, norep), + PROC(LOCK_MSG, lockargs, norep), + PROC(CANCEL_MSG, cancargs, norep), + PROC(UNLOCK_MSG, unlockargs, norep), + PROC(GRANTED_MSG, testargs, norep), + PROC(TEST_RES, testres, norep), + PROC(LOCK_RES, res, norep), + PROC(CANCEL_RES, res, norep), + PROC(UNLOCK_RES, res, norep), + PROC(GRANTED_RES, res, norep), +}; + +static const struct rpc_version nlm_version1 = { + .number = 1, + .nrprocs = ARRAY_SIZE(nlm_procedures), + .procs = nlm_procedures, +}; + +static const struct rpc_version nlm_version3 = { + .number = 3, + .nrprocs = ARRAY_SIZE(nlm_procedures), + .procs = nlm_procedures, +}; + +static const struct rpc_version *nlm_versions[] = { + [1] = &nlm_version1, + [3] = &nlm_version3, +#ifdef CONFIG_LOCKD_V4 + [4] = &nlm_version4, +#endif +}; + +static struct rpc_stat nlm_rpc_stats; + +const struct rpc_program nlm_program = { + .name = "lockd", + .number = NLM_PROGRAM, + .nrvers = ARRAY_SIZE(nlm_versions), + .version = nlm_versions, + .stats = &nlm_rpc_stats, +}; diff --git a/fs/lockd/grace.c b/fs/lockd/grace.c new file mode 100644 index 00000000000..6d1ee7204c8 --- /dev/null +++ b/fs/lockd/grace.c @@ -0,0 +1,65 @@ +/* + * Common code for control of lockd and nfsv4 grace periods. + */ + +#include <linux/module.h> +#include <linux/lockd/bind.h> +#include <net/net_namespace.h> + +#include "netns.h" + +static DEFINE_SPINLOCK(grace_lock); + +/** + * locks_start_grace + * @lm: who this grace period is for + * + * A grace period is a period during which locks should not be given + * out. Currently grace periods are only enforced by the two lock + * managers (lockd and nfsd), using the locks_in_grace() function to + * check when they are in a grace period. + * + * This function is called to start a grace period. + */ +void locks_start_grace(struct net *net, struct lock_manager *lm) +{ + struct lockd_net *ln = net_generic(net, lockd_net_id); + + spin_lock(&grace_lock); + list_add(&lm->list, &ln->grace_list); + spin_unlock(&grace_lock); +} +EXPORT_SYMBOL_GPL(locks_start_grace); + +/** + * locks_end_grace + * @lm: who this grace period is for + * + * Call this function to state that the given lock manager is ready to + * resume regular locking. The grace period will not end until all lock + * managers that called locks_start_grace() also call locks_end_grace(). + * Note that callers count on it being safe to call this more than once, + * and the second call should be a no-op. + */ +void locks_end_grace(struct lock_manager *lm) +{ + spin_lock(&grace_lock); + list_del_init(&lm->list); + spin_unlock(&grace_lock); +} +EXPORT_SYMBOL_GPL(locks_end_grace); + +/** + * locks_in_grace + * + * Lock managers call this function to determine when it is OK for them + * to answer ordinary lock requests, and when they should accept only + * lock reclaims. + */ +int locks_in_grace(struct net *net) +{ + struct lockd_net *ln = net_generic(net, lockd_net_id); + + return !list_empty(&ln->grace_list); +} +EXPORT_SYMBOL_GPL(locks_in_grace); diff --git a/fs/lockd/host.c b/fs/lockd/host.c index 82f7a0b1d8a..969d589c848 100644 --- a/fs/lockd/host.c +++ b/fs/lockd/host.c @@ -9,153 +9,408 @@ */ #include <linux/types.h> -#include <linux/sched.h> #include <linux/slab.h> #include <linux/in.h> +#include <linux/in6.h> #include <linux/sunrpc/clnt.h> +#include <linux/sunrpc/addr.h> #include <linux/sunrpc/svc.h> #include <linux/lockd/lockd.h> -#include <linux/lockd/sm_inter.h> +#include <linux/mutex.h> +#include <linux/sunrpc/svc_xprt.h> + +#include <net/ipv6.h> + +#include "netns.h" #define NLMDBG_FACILITY NLMDBG_HOSTCACHE -#define NLM_HOST_MAX 64 #define NLM_HOST_NRHASH 32 -#define NLM_ADDRHASH(addr) (ntohl(addr) & (NLM_HOST_NRHASH-1)) #define NLM_HOST_REBIND (60 * HZ) -#define NLM_HOST_EXPIRE ((nrhosts > NLM_HOST_MAX)? 300 * HZ : 120 * HZ) -#define NLM_HOST_COLLECT ((nrhosts > NLM_HOST_MAX)? 120 * HZ : 60 * HZ) -#define NLM_HOST_ADDR(sv) (&(sv)->s_nlmclnt->cl_xprt->addr) +#define NLM_HOST_EXPIRE (300 * HZ) +#define NLM_HOST_COLLECT (120 * HZ) + +static struct hlist_head nlm_server_hosts[NLM_HOST_NRHASH]; +static struct hlist_head nlm_client_hosts[NLM_HOST_NRHASH]; + +#define for_each_host(host, chain, table) \ + for ((chain) = (table); \ + (chain) < (table) + NLM_HOST_NRHASH; ++(chain)) \ + hlist_for_each_entry((host), (chain), h_hash) + +#define for_each_host_safe(host, next, chain, table) \ + for ((chain) = (table); \ + (chain) < (table) + NLM_HOST_NRHASH; ++(chain)) \ + hlist_for_each_entry_safe((host), (next), \ + (chain), h_hash) + +static unsigned long nrhosts; +static DEFINE_MUTEX(nlm_host_mutex); + +static void nlm_gc_hosts(struct net *net); + +struct nlm_lookup_host_info { + const int server; /* search for server|client */ + const struct sockaddr *sap; /* address to search for */ + const size_t salen; /* it's length */ + const unsigned short protocol; /* transport to search for*/ + const u32 version; /* NLM version to search for */ + const char *hostname; /* remote's hostname */ + const size_t hostname_len; /* it's length */ + const int noresvport; /* use non-priv port */ + struct net *net; /* network namespace to bind */ +}; -static struct nlm_host * nlm_hosts[NLM_HOST_NRHASH]; -static unsigned long next_gc; -static int nrhosts; -static DECLARE_MUTEX(nlm_host_sema); +/* + * Hash function must work well on big- and little-endian platforms + */ +static unsigned int __nlm_hash32(const __be32 n) +{ + unsigned int hash = (__force u32)n ^ ((__force u32)n >> 16); + return hash ^ (hash >> 8); +} +static unsigned int __nlm_hash_addr4(const struct sockaddr *sap) +{ + const struct sockaddr_in *sin = (struct sockaddr_in *)sap; + return __nlm_hash32(sin->sin_addr.s_addr); +} -static void nlm_gc_hosts(void); +static unsigned int __nlm_hash_addr6(const struct sockaddr *sap) +{ + const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sap; + const struct in6_addr addr = sin6->sin6_addr; + return __nlm_hash32(addr.s6_addr32[0]) ^ + __nlm_hash32(addr.s6_addr32[1]) ^ + __nlm_hash32(addr.s6_addr32[2]) ^ + __nlm_hash32(addr.s6_addr32[3]); +} + +static unsigned int nlm_hash_address(const struct sockaddr *sap) +{ + unsigned int hash; + + switch (sap->sa_family) { + case AF_INET: + hash = __nlm_hash_addr4(sap); + break; + case AF_INET6: + hash = __nlm_hash_addr6(sap); + break; + default: + hash = 0; + } + return hash & (NLM_HOST_NRHASH - 1); +} /* - * Find an NLM server handle in the cache. If there is none, create it. + * Allocate and initialize an nlm_host. Common to both client and server. */ -struct nlm_host * -nlmclnt_lookup_host(struct sockaddr_in *sin, int proto, int version) +static struct nlm_host *nlm_alloc_host(struct nlm_lookup_host_info *ni, + struct nsm_handle *nsm) { - return nlm_lookup_host(0, sin, proto, version); + struct nlm_host *host = NULL; + unsigned long now = jiffies; + + if (nsm != NULL) + atomic_inc(&nsm->sm_count); + else { + host = NULL; + nsm = nsm_get_handle(ni->sap, ni->salen, + ni->hostname, ni->hostname_len); + if (unlikely(nsm == NULL)) { + dprintk("lockd: %s failed; no nsm handle\n", + __func__); + goto out; + } + } + + host = kmalloc(sizeof(*host), GFP_KERNEL); + if (unlikely(host == NULL)) { + dprintk("lockd: %s failed; no memory\n", __func__); + nsm_release(nsm); + goto out; + } + + memcpy(nlm_addr(host), ni->sap, ni->salen); + host->h_addrlen = ni->salen; + rpc_set_port(nlm_addr(host), 0); + host->h_srcaddrlen = 0; + + host->h_rpcclnt = NULL; + host->h_name = nsm->sm_name; + host->h_version = ni->version; + host->h_proto = ni->protocol; + host->h_reclaiming = 0; + host->h_server = ni->server; + host->h_noresvport = ni->noresvport; + host->h_inuse = 0; + init_waitqueue_head(&host->h_gracewait); + init_rwsem(&host->h_rwsem); + host->h_state = 0; + host->h_nsmstate = 0; + host->h_pidcount = 0; + atomic_set(&host->h_count, 1); + mutex_init(&host->h_mutex); + host->h_nextrebind = now + NLM_HOST_REBIND; + host->h_expires = now + NLM_HOST_EXPIRE; + INIT_LIST_HEAD(&host->h_lockowners); + spin_lock_init(&host->h_lock); + INIT_LIST_HEAD(&host->h_granted); + INIT_LIST_HEAD(&host->h_reclaim); + host->h_nsmhandle = nsm; + host->h_addrbuf = nsm->sm_addrbuf; + host->net = ni->net; + +out: + return host; } /* - * Find an NLM client handle in the cache. If there is none, create it. + * Destroy an nlm_host and free associated resources + * + * Caller must hold nlm_host_mutex. */ -struct nlm_host * -nlmsvc_lookup_host(struct svc_rqst *rqstp) +static void nlm_destroy_host_locked(struct nlm_host *host) { - return nlm_lookup_host(1, &rqstp->rq_addr, - rqstp->rq_prot, rqstp->rq_vers); + struct rpc_clnt *clnt; + struct lockd_net *ln = net_generic(host->net, lockd_net_id); + + dprintk("lockd: destroy host %s\n", host->h_name); + + hlist_del_init(&host->h_hash); + + nsm_unmonitor(host); + nsm_release(host->h_nsmhandle); + + clnt = host->h_rpcclnt; + if (clnt != NULL) + rpc_shutdown_client(clnt); + kfree(host); + + ln->nrhosts--; + nrhosts--; } -/* - * Common host lookup routine for server & client +/** + * nlmclnt_lookup_host - Find an NLM host handle matching a remote server + * @sap: network address of server + * @salen: length of server address + * @protocol: transport protocol to use + * @version: NLM protocol version + * @hostname: '\0'-terminated hostname of server + * @noresvport: 1 if non-privileged port should be used + * + * Returns an nlm_host structure that matches the passed-in + * [server address, transport protocol, NLM version, server hostname]. + * If one doesn't already exist in the host cache, a new handle is + * created and returned. */ -struct nlm_host * -nlm_lookup_host(int server, struct sockaddr_in *sin, - int proto, int version) +struct nlm_host *nlmclnt_lookup_host(const struct sockaddr *sap, + const size_t salen, + const unsigned short protocol, + const u32 version, + const char *hostname, + int noresvport, + struct net *net) { - struct nlm_host *host, **hp; - u32 addr; - int hash; + struct nlm_lookup_host_info ni = { + .server = 0, + .sap = sap, + .salen = salen, + .protocol = protocol, + .version = version, + .hostname = hostname, + .hostname_len = strlen(hostname), + .noresvport = noresvport, + .net = net, + }; + struct hlist_head *chain; + struct nlm_host *host; + struct nsm_handle *nsm = NULL; + struct lockd_net *ln = net_generic(net, lockd_net_id); - dprintk("lockd: nlm_lookup_host(%08x, p=%d, v=%d)\n", - (unsigned)(sin? ntohl(sin->sin_addr.s_addr) : 0), proto, version); + dprintk("lockd: %s(host='%s', vers=%u, proto=%s)\n", __func__, + (hostname ? hostname : "<none>"), version, + (protocol == IPPROTO_UDP ? "udp" : "tcp")); - hash = NLM_ADDRHASH(sin->sin_addr.s_addr); + mutex_lock(&nlm_host_mutex); - /* Lock hash table */ - down(&nlm_host_sema); + chain = &nlm_client_hosts[nlm_hash_address(sap)]; + hlist_for_each_entry(host, chain, h_hash) { + if (host->net != net) + continue; + if (!rpc_cmp_addr(nlm_addr(host), sap)) + continue; - if (time_after_eq(jiffies, next_gc)) - nlm_gc_hosts(); + /* Same address. Share an NSM handle if we already have one */ + if (nsm == NULL) + nsm = host->h_nsmhandle; - for (hp = &nlm_hosts[hash]; (host = *hp) != 0; hp = &host->h_next) { - if (host->h_proto != proto) + if (host->h_proto != protocol) continue; if (host->h_version != version) continue; - if (host->h_server != server) - continue; - if (nlm_cmp_addr(&host->h_addr, sin)) { - if (hp != nlm_hosts + hash) { - *hp = host->h_next; - host->h_next = nlm_hosts[hash]; - nlm_hosts[hash] = host; - } - nlm_get_host(host); - up(&nlm_host_sema); - return host; - } + nlm_get_host(host); + dprintk("lockd: %s found host %s (%s)\n", __func__, + host->h_name, host->h_addrbuf); + goto out; } - /* Ooops, no host found, create it */ - dprintk("lockd: creating host entry\n"); + host = nlm_alloc_host(&ni, nsm); + if (unlikely(host == NULL)) + goto out; - if (!(host = (struct nlm_host *) kmalloc(sizeof(*host), GFP_KERNEL))) - goto nohost; - memset(host, 0, sizeof(*host)); + hlist_add_head(&host->h_hash, chain); + ln->nrhosts++; + nrhosts++; - addr = sin->sin_addr.s_addr; - sprintf(host->h_name, "%u.%u.%u.%u", NIPQUAD(addr)); + dprintk("lockd: %s created host %s (%s)\n", __func__, + host->h_name, host->h_addrbuf); - host->h_addr = *sin; - host->h_addr.sin_port = 0; /* ouch! */ - host->h_version = version; - host->h_proto = proto; - host->h_rpcclnt = NULL; - init_MUTEX(&host->h_sema); - host->h_nextrebind = jiffies + NLM_HOST_REBIND; - host->h_expires = jiffies + NLM_HOST_EXPIRE; - atomic_set(&host->h_count, 1); - init_waitqueue_head(&host->h_gracewait); - host->h_state = 0; /* pseudo NSM state */ - host->h_nsmstate = 0; /* real NSM state */ - host->h_server = server; - host->h_next = nlm_hosts[hash]; - nlm_hosts[hash] = host; - INIT_LIST_HEAD(&host->h_lockowners); - spin_lock_init(&host->h_lock); +out: + mutex_unlock(&nlm_host_mutex); + return host; +} - if (++nrhosts > NLM_HOST_MAX) - next_gc = 0; +/** + * nlmclnt_release_host - release client nlm_host + * @host: nlm_host to release + * + */ +void nlmclnt_release_host(struct nlm_host *host) +{ + if (host == NULL) + return; -nohost: - up(&nlm_host_sema); - return host; + dprintk("lockd: release client host %s\n", host->h_name); + + WARN_ON_ONCE(host->h_server); + + if (atomic_dec_and_test(&host->h_count)) { + WARN_ON_ONCE(!list_empty(&host->h_lockowners)); + WARN_ON_ONCE(!list_empty(&host->h_granted)); + WARN_ON_ONCE(!list_empty(&host->h_reclaim)); + + mutex_lock(&nlm_host_mutex); + nlm_destroy_host_locked(host); + mutex_unlock(&nlm_host_mutex); + } } -struct nlm_host * -nlm_find_client(void) +/** + * nlmsvc_lookup_host - Find an NLM host handle matching a remote client + * @rqstp: incoming NLM request + * @hostname: name of client host + * @hostname_len: length of client hostname + * + * Returns an nlm_host structure that matches the [client address, + * transport protocol, NLM version, client hostname] of the passed-in + * NLM request. If one doesn't already exist in the host cache, a + * new handle is created and returned. + * + * Before possibly creating a new nlm_host, construct a sockaddr + * for a specific source address in case the local system has + * multiple network addresses. The family of the address in + * rq_daddr is guaranteed to be the same as the family of the + * address in rq_addr, so it's safe to use the same family for + * the source address. + */ +struct nlm_host *nlmsvc_lookup_host(const struct svc_rqst *rqstp, + const char *hostname, + const size_t hostname_len) { - /* find a nlm_host for a client for which h_killed == 0. - * and return it - */ - int hash; - down(&nlm_host_sema); - for (hash = 0 ; hash < NLM_HOST_NRHASH; hash++) { - struct nlm_host *host, **hp; - for (hp = &nlm_hosts[hash]; (host = *hp) != 0; hp = &host->h_next) { - if (host->h_server && - host->h_killed == 0) { - nlm_get_host(host); - up(&nlm_host_sema); - return host; - } - } + struct hlist_head *chain; + struct nlm_host *host = NULL; + struct nsm_handle *nsm = NULL; + struct sockaddr *src_sap = svc_daddr(rqstp); + size_t src_len = rqstp->rq_daddrlen; + struct net *net = SVC_NET(rqstp); + struct nlm_lookup_host_info ni = { + .server = 1, + .sap = svc_addr(rqstp), + .salen = rqstp->rq_addrlen, + .protocol = rqstp->rq_prot, + .version = rqstp->rq_vers, + .hostname = hostname, + .hostname_len = hostname_len, + .net = net, + }; + struct lockd_net *ln = net_generic(net, lockd_net_id); + + dprintk("lockd: %s(host='%*s', vers=%u, proto=%s)\n", __func__, + (int)hostname_len, hostname, rqstp->rq_vers, + (rqstp->rq_prot == IPPROTO_UDP ? "udp" : "tcp")); + + mutex_lock(&nlm_host_mutex); + + if (time_after_eq(jiffies, ln->next_gc)) + nlm_gc_hosts(net); + + chain = &nlm_server_hosts[nlm_hash_address(ni.sap)]; + hlist_for_each_entry(host, chain, h_hash) { + if (host->net != net) + continue; + if (!rpc_cmp_addr(nlm_addr(host), ni.sap)) + continue; + + /* Same address. Share an NSM handle if we already have one */ + if (nsm == NULL) + nsm = host->h_nsmhandle; + + if (host->h_proto != ni.protocol) + continue; + if (host->h_version != ni.version) + continue; + if (!rpc_cmp_addr(nlm_srcaddr(host), src_sap)) + continue; + + /* Move to head of hash chain. */ + hlist_del(&host->h_hash); + hlist_add_head(&host->h_hash, chain); + + nlm_get_host(host); + dprintk("lockd: %s found host %s (%s)\n", + __func__, host->h_name, host->h_addrbuf); + goto out; } - up(&nlm_host_sema); - return NULL; + + host = nlm_alloc_host(&ni, nsm); + if (unlikely(host == NULL)) + goto out; + + memcpy(nlm_srcaddr(host), src_sap, src_len); + host->h_srcaddrlen = src_len; + hlist_add_head(&host->h_hash, chain); + ln->nrhosts++; + nrhosts++; + + dprintk("lockd: %s created host %s (%s)\n", + __func__, host->h_name, host->h_addrbuf); + +out: + mutex_unlock(&nlm_host_mutex); + return host; +} + +/** + * nlmsvc_release_host - release server nlm_host + * @host: nlm_host to release + * + * Host is destroyed later in nlm_gc_host(). + */ +void nlmsvc_release_host(struct nlm_host *host) +{ + if (host == NULL) + return; + + dprintk("lockd: release server host %s\n", host->h_name); + + WARN_ON_ONCE(!host->h_server); + atomic_dec(&host->h_count); } - /* * Create the NLM RPC client for an NLM peer */ @@ -163,50 +418,68 @@ struct rpc_clnt * nlm_bind_host(struct nlm_host *host) { struct rpc_clnt *clnt; - struct rpc_xprt *xprt; - dprintk("lockd: nlm_bind_host(%08x)\n", - (unsigned)ntohl(host->h_addr.sin_addr.s_addr)); + dprintk("lockd: nlm_bind_host %s (%s)\n", + host->h_name, host->h_addrbuf); /* Lock host handle */ - down(&host->h_sema); + mutex_lock(&host->h_mutex); /* If we've already created an RPC client, check whether * RPC rebind is required */ if ((clnt = host->h_rpcclnt) != NULL) { - xprt = clnt->cl_xprt; if (time_after_eq(jiffies, host->h_nextrebind)) { rpc_force_rebind(clnt); host->h_nextrebind = jiffies + NLM_HOST_REBIND; - dprintk("lockd: next rebind in %ld jiffies\n", + dprintk("lockd: next rebind in %lu jiffies\n", host->h_nextrebind - jiffies); } } else { - xprt = xprt_create_proto(host->h_proto, &host->h_addr, NULL); - if (IS_ERR(xprt)) - goto forgetit; - - xprt_set_timeout(&xprt->timeout, 5, nlmsvc_timeout); - xprt->resvport = 1; /* NLM requires a reserved port */ - - /* Existing NLM servers accept AUTH_UNIX only */ - clnt = rpc_create_client(xprt, host->h_name, &nlm_program, - host->h_version, RPC_AUTH_UNIX); - if (IS_ERR(clnt)) - goto forgetit; - clnt->cl_autobind = 1; /* turn on pmap queries */ - - host->h_rpcclnt = clnt; + unsigned long increment = nlmsvc_timeout; + struct rpc_timeout timeparms = { + .to_initval = increment, + .to_increment = increment, + .to_maxval = increment * 6UL, + .to_retries = 5U, + }; + struct rpc_create_args args = { + .net = host->net, + .protocol = host->h_proto, + .address = nlm_addr(host), + .addrsize = host->h_addrlen, + .timeout = &timeparms, + .servername = host->h_name, + .program = &nlm_program, + .version = host->h_version, + .authflavor = RPC_AUTH_UNIX, + .flags = (RPC_CLNT_CREATE_NOPING | + RPC_CLNT_CREATE_AUTOBIND), + }; + + /* + * lockd retries server side blocks automatically so we want + * those to be soft RPC calls. Client side calls need to be + * hard RPC tasks. + */ + if (!host->h_server) + args.flags |= RPC_CLNT_CREATE_HARDRTRY; + if (host->h_noresvport) + args.flags |= RPC_CLNT_CREATE_NONPRIVPORT; + if (host->h_srcaddrlen) + args.saddress = nlm_srcaddr(host); + + clnt = rpc_create(&args); + if (!IS_ERR(clnt)) + host->h_rpcclnt = clnt; + else { + printk("lockd: couldn't create RPC handle for %s\n", host->h_name); + clnt = NULL; + } } - up(&host->h_sema); + mutex_unlock(&host->h_mutex); return clnt; - -forgetit: - printk("lockd: couldn't create RPC handle for %s\n", host->h_name); - up(&host->h_sema); - return NULL; } /* @@ -235,54 +508,127 @@ struct nlm_host * nlm_get_host(struct nlm_host *host) return host; } -/* - * Release NLM host after use - */ -void nlm_release_host(struct nlm_host *host) +static struct nlm_host *next_host_state(struct hlist_head *cache, + struct nsm_handle *nsm, + const struct nlm_reboot *info) { - if (host != NULL) { - dprintk("lockd: release host %s\n", host->h_name); - atomic_dec(&host->h_count); - BUG_ON(atomic_read(&host->h_count) < 0); + struct nlm_host *host; + struct hlist_head *chain; + + mutex_lock(&nlm_host_mutex); + for_each_host(host, chain, cache) { + if (host->h_nsmhandle == nsm + && host->h_nsmstate != info->state) { + host->h_nsmstate = info->state; + host->h_state++; + + nlm_get_host(host); + mutex_unlock(&nlm_host_mutex); + return host; + } } + + mutex_unlock(&nlm_host_mutex); + return NULL; } -/* - * Shut down the hosts module. - * Note that this routine is called only at server shutdown time. +/** + * nlm_host_rebooted - Release all resources held by rebooted host + * @info: pointer to decoded results of NLM_SM_NOTIFY call + * + * We were notified that the specified host has rebooted. Release + * all resources held by that peer. */ -void -nlm_shutdown_hosts(void) +void nlm_host_rebooted(const struct nlm_reboot *info) { + struct nsm_handle *nsm; struct nlm_host *host; - int i; - dprintk("lockd: shutting down host module\n"); - down(&nlm_host_sema); + nsm = nsm_reboot_lookup(info); + if (unlikely(nsm == NULL)) + return; - /* First, make all hosts eligible for gc */ - dprintk("lockd: nuking all hosts...\n"); - for (i = 0; i < NLM_HOST_NRHASH; i++) { - for (host = nlm_hosts[i]; host; host = host->h_next) - host->h_expires = jiffies - 1; + /* Mark all hosts tied to this NSM state as having rebooted. + * We run the loop repeatedly, because we drop the host table + * lock for this. + * To avoid processing a host several times, we match the nsmstate. + */ + while ((host = next_host_state(nlm_server_hosts, nsm, info)) != NULL) { + nlmsvc_free_host_resources(host); + nlmsvc_release_host(host); + } + while ((host = next_host_state(nlm_client_hosts, nsm, info)) != NULL) { + nlmclnt_recovery(host); + nlmclnt_release_host(host); } - /* Then, perform a garbage collection pass */ - nlm_gc_hosts(); - up(&nlm_host_sema); + nsm_release(nsm); +} + +static void nlm_complain_hosts(struct net *net) +{ + struct hlist_head *chain; + struct nlm_host *host; + + if (net) { + struct lockd_net *ln = net_generic(net, lockd_net_id); - /* complain if any hosts are left */ - if (nrhosts) { + if (ln->nrhosts == 0) + return; + printk(KERN_WARNING "lockd: couldn't shutdown host module for net %p!\n", net); + dprintk("lockd: %lu hosts left in net %p:\n", ln->nrhosts, net); + } else { + if (nrhosts == 0) + return; printk(KERN_WARNING "lockd: couldn't shutdown host module!\n"); - dprintk("lockd: %d hosts left:\n", nrhosts); - for (i = 0; i < NLM_HOST_NRHASH; i++) { - for (host = nlm_hosts[i]; host; host = host->h_next) { - dprintk(" %s (cnt %d use %d exp %ld)\n", - host->h_name, atomic_read(&host->h_count), - host->h_inuse, host->h_expires); - } + dprintk("lockd: %lu hosts left:\n", nrhosts); + } + + for_each_host(host, chain, nlm_server_hosts) { + if (net && host->net != net) + continue; + dprintk(" %s (cnt %d use %d exp %ld net %p)\n", + host->h_name, atomic_read(&host->h_count), + host->h_inuse, host->h_expires, host->net); + } +} + +void +nlm_shutdown_hosts_net(struct net *net) +{ + struct hlist_head *chain; + struct nlm_host *host; + + mutex_lock(&nlm_host_mutex); + + /* First, make all hosts eligible for gc */ + dprintk("lockd: nuking all hosts in net %p...\n", net); + for_each_host(host, chain, nlm_server_hosts) { + if (net && host->net != net) + continue; + host->h_expires = jiffies - 1; + if (host->h_rpcclnt) { + rpc_shutdown_client(host->h_rpcclnt); + host->h_rpcclnt = NULL; } } + + /* Then, perform a garbage collection pass */ + nlm_gc_hosts(net); + mutex_unlock(&nlm_host_mutex); + + nlm_complain_hosts(net); +} + +/* + * Shut down the hosts module. + * Note that this routine is called only at server shutdown time. + */ +void +nlm_shutdown_hosts(void) +{ + dprintk("lockd: shutting down host module\n"); + nlm_shutdown_hosts_net(NULL); } /* @@ -291,52 +637,39 @@ nlm_shutdown_hosts(void) * mark & sweep for resources held by remote clients. */ static void -nlm_gc_hosts(void) +nlm_gc_hosts(struct net *net) { - struct nlm_host **q, *host; - struct rpc_clnt *clnt; - int i; + struct hlist_head *chain; + struct hlist_node *next; + struct nlm_host *host; - dprintk("lockd: host garbage collection\n"); - for (i = 0; i < NLM_HOST_NRHASH; i++) { - for (host = nlm_hosts[i]; host; host = host->h_next) - host->h_inuse = 0; + dprintk("lockd: host garbage collection for net %p\n", net); + for_each_host(host, chain, nlm_server_hosts) { + if (net && host->net != net) + continue; + host->h_inuse = 0; } /* Mark all hosts that hold locks, blocks or shares */ - nlmsvc_mark_resources(); - - for (i = 0; i < NLM_HOST_NRHASH; i++) { - q = &nlm_hosts[i]; - while ((host = *q) != NULL) { - if (atomic_read(&host->h_count) || host->h_inuse - || time_before(jiffies, host->h_expires)) { - dprintk("nlm_gc_hosts skipping %s (cnt %d use %d exp %ld)\n", - host->h_name, atomic_read(&host->h_count), - host->h_inuse, host->h_expires); - q = &host->h_next; - continue; - } - dprintk("lockd: delete host %s\n", host->h_name); - *q = host->h_next; - /* Don't unmonitor hosts that have been invalidated */ - if (host->h_monitored && !host->h_killed) - nsm_unmonitor(host); - if ((clnt = host->h_rpcclnt) != NULL) { - if (atomic_read(&clnt->cl_users)) { - printk(KERN_WARNING - "lockd: active RPC handle\n"); - clnt->cl_dead = 1; - } else { - rpc_destroy_client(host->h_rpcclnt); - } - } - BUG_ON(!list_empty(&host->h_lockowners)); - kfree(host); - nrhosts--; + nlmsvc_mark_resources(net); + + for_each_host_safe(host, next, chain, nlm_server_hosts) { + if (net && host->net != net) + continue; + if (atomic_read(&host->h_count) || host->h_inuse + || time_before(jiffies, host->h_expires)) { + dprintk("nlm_gc_hosts skipping %s " + "(cnt %d use %d exp %ld net %p)\n", + host->h_name, atomic_read(&host->h_count), + host->h_inuse, host->h_expires, host->net); + continue; } + nlm_destroy_host_locked(host); } - next_gc = jiffies + NLM_HOST_COLLECT; -} + if (net) { + struct lockd_net *ln = net_generic(net, lockd_net_id); + ln->next_gc = jiffies + NLM_HOST_COLLECT; + } +} diff --git a/fs/lockd/mon.c b/fs/lockd/mon.c index 0edc03e6796..1812f026960 100644 --- a/fs/lockd/mon.c +++ b/fs/lockd/mon.c @@ -7,238 +7,607 @@ */ #include <linux/types.h> -#include <linux/utsname.h> #include <linux/kernel.h> +#include <linux/ktime.h> +#include <linux/slab.h> + #include <linux/sunrpc/clnt.h> +#include <linux/sunrpc/addr.h> +#include <linux/sunrpc/xprtsock.h> #include <linux/sunrpc/svc.h> #include <linux/lockd/lockd.h> -#include <linux/lockd/sm_inter.h> +#include <asm/unaligned.h> + +#include "netns.h" #define NLMDBG_FACILITY NLMDBG_MONITOR +#define NSM_PROGRAM 100024 +#define NSM_VERSION 1 + +enum { + NSMPROC_NULL, + NSMPROC_STAT, + NSMPROC_MON, + NSMPROC_UNMON, + NSMPROC_UNMON_ALL, + NSMPROC_SIMU_CRASH, + NSMPROC_NOTIFY, +}; -static struct rpc_clnt * nsm_create(void); +struct nsm_args { + struct nsm_private *priv; + u32 prog; /* RPC callback info */ + u32 vers; + u32 proc; -static struct rpc_program nsm_program; + char *mon_name; + char *nodename; +}; + +struct nsm_res { + u32 status; + u32 state; +}; + +static const struct rpc_program nsm_program; +static LIST_HEAD(nsm_handles); +static DEFINE_SPINLOCK(nsm_lock); /* * Local NSM state */ -u32 nsm_local_state; +u32 __read_mostly nsm_local_state; +bool __read_mostly nsm_use_hostnames; -/* - * Common procedure for SM_MON/SM_UNMON calls - */ -static int -nsm_mon_unmon(struct nlm_host *host, u32 proc, struct nsm_res *res) +static inline struct sockaddr *nsm_addr(const struct nsm_handle *nsm) { - struct rpc_clnt *clnt; - int status; - struct nsm_args args; + return (struct sockaddr *)&nsm->sm_addr; +} - clnt = nsm_create(); - if (IS_ERR(clnt)) { - status = PTR_ERR(clnt); +static struct rpc_clnt *nsm_create(struct net *net) +{ + struct sockaddr_in sin = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), + }; + struct rpc_create_args args = { + .net = net, + .protocol = XPRT_TRANSPORT_TCP, + .address = (struct sockaddr *)&sin, + .addrsize = sizeof(sin), + .servername = "rpc.statd", + .program = &nsm_program, + .version = NSM_VERSION, + .authflavor = RPC_AUTH_NULL, + .flags = RPC_CLNT_CREATE_NOPING, + }; + + return rpc_create(&args); +} + +static struct rpc_clnt *nsm_client_set(struct lockd_net *ln, + struct rpc_clnt *clnt) +{ + spin_lock(&ln->nsm_clnt_lock); + if (ln->nsm_users == 0) { + if (clnt == NULL) + goto out; + ln->nsm_clnt = clnt; + } + clnt = ln->nsm_clnt; + ln->nsm_users++; +out: + spin_unlock(&ln->nsm_clnt_lock); + return clnt; +} + +static struct rpc_clnt *nsm_client_get(struct net *net) +{ + struct rpc_clnt *clnt, *new; + struct lockd_net *ln = net_generic(net, lockd_net_id); + + clnt = nsm_client_set(ln, NULL); + if (clnt != NULL) goto out; + + clnt = new = nsm_create(net); + if (IS_ERR(clnt)) + goto out; + + clnt = nsm_client_set(ln, new); + if (clnt != new) + rpc_shutdown_client(new); +out: + return clnt; +} + +static void nsm_client_put(struct net *net) +{ + struct lockd_net *ln = net_generic(net, lockd_net_id); + struct rpc_clnt *clnt = NULL; + + spin_lock(&ln->nsm_clnt_lock); + ln->nsm_users--; + if (ln->nsm_users == 0) { + clnt = ln->nsm_clnt; + ln->nsm_clnt = NULL; } + spin_unlock(&ln->nsm_clnt_lock); + if (clnt != NULL) + rpc_shutdown_client(clnt); +} + +static int nsm_mon_unmon(struct nsm_handle *nsm, u32 proc, struct nsm_res *res, + struct rpc_clnt *clnt) +{ + int status; + struct nsm_args args = { + .priv = &nsm->sm_priv, + .prog = NLM_PROGRAM, + .vers = 3, + .proc = NLMPROC_NSM_NOTIFY, + .mon_name = nsm->sm_mon_name, + .nodename = clnt->cl_nodename, + }; + struct rpc_message msg = { + .rpc_argp = &args, + .rpc_resp = res, + }; - args.addr = host->h_addr.sin_addr.s_addr; - args.proto= (host->h_proto<<1) | host->h_server; - args.prog = NLM_PROGRAM; - args.vers = host->h_version; - args.proc = NLMPROC_NSM_NOTIFY; memset(res, 0, sizeof(*res)); - status = rpc_call(clnt, proc, &args, res, 0); + msg.rpc_proc = &clnt->cl_procinfo[proc]; + status = rpc_call_sync(clnt, &msg, RPC_TASK_SOFTCONN); if (status < 0) - printk(KERN_DEBUG "nsm_mon_unmon: rpc failed, status=%d\n", - status); + dprintk("lockd: NSM upcall RPC failed, status=%d\n", + status); else status = 0; - out: return status; } -/* - * Set up monitoring of a remote host +/** + * nsm_monitor - Notify a peer in case we reboot + * @host: pointer to nlm_host of peer to notify + * + * If this peer is not already monitored, this function sends an + * upcall to the local rpc.statd to record the name/address of + * the peer to notify in case we reboot. + * + * Returns zero if the peer is monitored by the local rpc.statd; + * otherwise a negative errno value is returned. */ -int -nsm_monitor(struct nlm_host *host) +int nsm_monitor(const struct nlm_host *host) { + struct nsm_handle *nsm = host->h_nsmhandle; struct nsm_res res; int status; + struct rpc_clnt *clnt; - dprintk("lockd: nsm_monitor(%s)\n", host->h_name); + dprintk("lockd: nsm_monitor(%s)\n", nsm->sm_name); - status = nsm_mon_unmon(host, SM_MON, &res); + if (nsm->sm_monitored) + return 0; - if (status < 0 || res.status != 0) - printk(KERN_NOTICE "lockd: cannot monitor %s\n", host->h_name); - else - host->h_monitored = 1; - return status; + /* + * Choose whether to record the caller_name or IP address of + * this peer in the local rpc.statd's database. + */ + nsm->sm_mon_name = nsm_use_hostnames ? nsm->sm_name : nsm->sm_addrbuf; + + clnt = nsm_client_get(host->net); + if (IS_ERR(clnt)) { + status = PTR_ERR(clnt); + dprintk("lockd: failed to create NSM upcall transport, " + "status=%d, net=%p\n", status, host->net); + return status; + } + + status = nsm_mon_unmon(nsm, NSMPROC_MON, &res, clnt); + if (unlikely(res.status != 0)) + status = -EIO; + if (unlikely(status < 0)) { + printk(KERN_NOTICE "lockd: cannot monitor %s\n", nsm->sm_name); + return status; + } + + nsm->sm_monitored = 1; + if (unlikely(nsm_local_state != res.state)) { + nsm_local_state = res.state; + dprintk("lockd: NSM state changed to %d\n", nsm_local_state); + } + return 0; } -/* - * Cease to monitor remote host +/** + * nsm_unmonitor - Unregister peer notification + * @host: pointer to nlm_host of peer to stop monitoring + * + * If this peer is monitored, this function sends an upcall to + * tell the local rpc.statd not to send this peer a notification + * when we reboot. */ -int -nsm_unmonitor(struct nlm_host *host) +void nsm_unmonitor(const struct nlm_host *host) { + struct nsm_handle *nsm = host->h_nsmhandle; struct nsm_res res; - int status; + int status; - dprintk("lockd: nsm_unmonitor(%s)\n", host->h_name); + if (atomic_read(&nsm->sm_count) == 1 + && nsm->sm_monitored && !nsm->sm_sticky) { + struct lockd_net *ln = net_generic(host->net, lockd_net_id); - status = nsm_mon_unmon(host, SM_UNMON, &res); - if (status < 0) - printk(KERN_NOTICE "lockd: cannot unmonitor %s\n", host->h_name); - else - host->h_monitored = 0; - return status; + dprintk("lockd: nsm_unmonitor(%s)\n", nsm->sm_name); + + status = nsm_mon_unmon(nsm, NSMPROC_UNMON, &res, ln->nsm_clnt); + if (res.status != 0) + status = -EIO; + if (status < 0) + printk(KERN_NOTICE "lockd: cannot unmonitor %s\n", + nsm->sm_name); + else + nsm->sm_monitored = 0; + + nsm_client_put(host->net); + } +} + +static struct nsm_handle *nsm_lookup_hostname(const char *hostname, + const size_t len) +{ + struct nsm_handle *nsm; + + list_for_each_entry(nsm, &nsm_handles, sm_link) + if (strlen(nsm->sm_name) == len && + memcmp(nsm->sm_name, hostname, len) == 0) + return nsm; + return NULL; +} + +static struct nsm_handle *nsm_lookup_addr(const struct sockaddr *sap) +{ + struct nsm_handle *nsm; + + list_for_each_entry(nsm, &nsm_handles, sm_link) + if (rpc_cmp_addr(nsm_addr(nsm), sap)) + return nsm; + return NULL; +} + +static struct nsm_handle *nsm_lookup_priv(const struct nsm_private *priv) +{ + struct nsm_handle *nsm; + + list_for_each_entry(nsm, &nsm_handles, sm_link) + if (memcmp(nsm->sm_priv.data, priv->data, + sizeof(priv->data)) == 0) + return nsm; + return NULL; } /* - * Create NSM client for the local host + * Construct a unique cookie to match this nsm_handle to this monitored + * host. It is passed to the local rpc.statd via NSMPROC_MON, and + * returned via NLMPROC_SM_NOTIFY, in the "priv" field of these + * requests. + * + * The NSM protocol requires that these cookies be unique while the + * system is running. We prefer a stronger requirement of making them + * unique across reboots. If user space bugs cause a stale cookie to + * be sent to the kernel, it could cause the wrong host to lose its + * lock state if cookies were not unique across reboots. + * + * The cookies are exposed only to local user space via loopback. They + * do not appear on the physical network. If we want greater security + * for some reason, nsm_init_private() could perform a one-way hash to + * obscure the contents of the cookie. */ -static struct rpc_clnt * -nsm_create(void) +static void nsm_init_private(struct nsm_handle *nsm) { - struct rpc_xprt *xprt; - struct rpc_clnt *clnt; - struct sockaddr_in sin; + u64 *p = (u64 *)&nsm->sm_priv.data; + struct timespec ts; + s64 ns; + + ktime_get_ts(&ts); + ns = timespec_to_ns(&ts); + put_unaligned(ns, p); + put_unaligned((unsigned long)nsm, p + 1); +} - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - sin.sin_port = 0; +static struct nsm_handle *nsm_create_handle(const struct sockaddr *sap, + const size_t salen, + const char *hostname, + const size_t hostname_len) +{ + struct nsm_handle *new; + + new = kzalloc(sizeof(*new) + hostname_len + 1, GFP_KERNEL); + if (unlikely(new == NULL)) + return NULL; + + atomic_set(&new->sm_count, 1); + new->sm_name = (char *)(new + 1); + memcpy(nsm_addr(new), sap, salen); + new->sm_addrlen = salen; + nsm_init_private(new); + + if (rpc_ntop(nsm_addr(new), new->sm_addrbuf, + sizeof(new->sm_addrbuf)) == 0) + (void)snprintf(new->sm_addrbuf, sizeof(new->sm_addrbuf), + "unsupported address family"); + memcpy(new->sm_name, hostname, hostname_len); + new->sm_name[hostname_len] = '\0'; + + return new; +} + +/** + * nsm_get_handle - Find or create a cached nsm_handle + * @sap: pointer to socket address of handle to find + * @salen: length of socket address + * @hostname: pointer to C string containing hostname to find + * @hostname_len: length of C string + * + * Behavior is modulated by the global nsm_use_hostnames variable. + * + * Returns a cached nsm_handle after bumping its ref count, or + * returns a fresh nsm_handle if a handle that matches @sap and/or + * @hostname cannot be found in the handle cache. Returns NULL if + * an error occurs. + */ +struct nsm_handle *nsm_get_handle(const struct sockaddr *sap, + const size_t salen, const char *hostname, + const size_t hostname_len) +{ + struct nsm_handle *cached, *new = NULL; + + if (hostname && memchr(hostname, '/', hostname_len) != NULL) { + if (printk_ratelimit()) { + printk(KERN_WARNING "Invalid hostname \"%.*s\" " + "in NFS lock request\n", + (int)hostname_len, hostname); + } + return NULL; + } - xprt = xprt_create_proto(IPPROTO_UDP, &sin, NULL); - if (IS_ERR(xprt)) - return (struct rpc_clnt *)xprt; - xprt->resvport = 1; /* NSM requires a reserved port */ +retry: + spin_lock(&nsm_lock); - clnt = rpc_create_client(xprt, "localhost", - &nsm_program, SM_VERSION, - RPC_AUTH_NULL); - if (IS_ERR(clnt)) - goto out_err; - clnt->cl_softrtry = 1; - clnt->cl_oneshot = 1; - return clnt; + if (nsm_use_hostnames && hostname != NULL) + cached = nsm_lookup_hostname(hostname, hostname_len); + else + cached = nsm_lookup_addr(sap); + + if (cached != NULL) { + atomic_inc(&cached->sm_count); + spin_unlock(&nsm_lock); + kfree(new); + dprintk("lockd: found nsm_handle for %s (%s), " + "cnt %d\n", cached->sm_name, + cached->sm_addrbuf, + atomic_read(&cached->sm_count)); + return cached; + } -out_err: - return clnt; + if (new != NULL) { + list_add(&new->sm_link, &nsm_handles); + spin_unlock(&nsm_lock); + dprintk("lockd: created nsm_handle for %s (%s)\n", + new->sm_name, new->sm_addrbuf); + return new; + } + + spin_unlock(&nsm_lock); + + new = nsm_create_handle(sap, salen, hostname, hostname_len); + if (unlikely(new == NULL)) + return NULL; + goto retry; +} + +/** + * nsm_reboot_lookup - match NLMPROC_SM_NOTIFY arguments to an nsm_handle + * @info: pointer to NLMPROC_SM_NOTIFY arguments + * + * Returns a matching nsm_handle if found in the nsm cache. The returned + * nsm_handle's reference count is bumped. Otherwise returns NULL if some + * error occurred. + */ +struct nsm_handle *nsm_reboot_lookup(const struct nlm_reboot *info) +{ + struct nsm_handle *cached; + + spin_lock(&nsm_lock); + + cached = nsm_lookup_priv(&info->priv); + if (unlikely(cached == NULL)) { + spin_unlock(&nsm_lock); + dprintk("lockd: never saw rebooted peer '%.*s' before\n", + info->len, info->mon); + return cached; + } + + atomic_inc(&cached->sm_count); + spin_unlock(&nsm_lock); + + dprintk("lockd: host %s (%s) rebooted, cnt %d\n", + cached->sm_name, cached->sm_addrbuf, + atomic_read(&cached->sm_count)); + return cached; +} + +/** + * nsm_release - Release an NSM handle + * @nsm: pointer to handle to be released + * + */ +void nsm_release(struct nsm_handle *nsm) +{ + if (atomic_dec_and_lock(&nsm->sm_count, &nsm_lock)) { + list_del(&nsm->sm_link); + spin_unlock(&nsm_lock); + dprintk("lockd: destroyed nsm_handle for %s (%s)\n", + nsm->sm_name, nsm->sm_addrbuf); + kfree(nsm); + } } /* * XDR functions for NSM. + * + * See http://www.opengroup.org/ for details on the Network + * Status Monitor wire protocol. */ -static u32 * -xdr_encode_common(struct rpc_rqst *rqstp, u32 *p, struct nsm_args *argp) +static void encode_nsm_string(struct xdr_stream *xdr, const char *string) { - char buffer[20]; + const u32 len = strlen(string); + __be32 *p; - /* - * Use the dotted-quad IP address of the remote host as - * identifier. Linux statd always looks up the canonical - * hostname first for whatever remote hostname it receives, - * so this works alright. - */ - sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(argp->addr)); - if (!(p = xdr_encode_string(p, buffer)) - || !(p = xdr_encode_string(p, system_utsname.nodename))) - return ERR_PTR(-EIO); - *p++ = htonl(argp->prog); - *p++ = htonl(argp->vers); - *p++ = htonl(argp->proc); - - return p; -} - -static int -xdr_encode_mon(struct rpc_rqst *rqstp, u32 *p, struct nsm_args *argp) -{ - p = xdr_encode_common(rqstp, p, argp); - if (IS_ERR(p)) - return PTR_ERR(p); - *p++ = argp->addr; - *p++ = argp->vers; - *p++ = argp->proto; - *p++ = 0; - rqstp->rq_slen = xdr_adjust_iovec(rqstp->rq_svec, p); - return 0; + p = xdr_reserve_space(xdr, 4 + len); + xdr_encode_opaque(p, string, len); } -static int -xdr_encode_unmon(struct rpc_rqst *rqstp, u32 *p, struct nsm_args *argp) +/* + * "mon_name" specifies the host to be monitored. + */ +static void encode_mon_name(struct xdr_stream *xdr, const struct nsm_args *argp) { - p = xdr_encode_common(rqstp, p, argp); - if (IS_ERR(p)) - return PTR_ERR(p); - rqstp->rq_slen = xdr_adjust_iovec(rqstp->rq_svec, p); - return 0; + encode_nsm_string(xdr, argp->mon_name); +} + +/* + * The "my_id" argument specifies the hostname and RPC procedure + * to be called when the status manager receives notification + * (via the NLMPROC_SM_NOTIFY call) that the state of host "mon_name" + * has changed. + */ +static void encode_my_id(struct xdr_stream *xdr, const struct nsm_args *argp) +{ + __be32 *p; + + encode_nsm_string(xdr, argp->nodename); + p = xdr_reserve_space(xdr, 4 + 4 + 4); + *p++ = cpu_to_be32(argp->prog); + *p++ = cpu_to_be32(argp->vers); + *p = cpu_to_be32(argp->proc); } -static int -xdr_decode_stat_res(struct rpc_rqst *rqstp, u32 *p, struct nsm_res *resp) +/* + * The "mon_id" argument specifies the non-private arguments + * of an NSMPROC_MON or NSMPROC_UNMON call. + */ +static void encode_mon_id(struct xdr_stream *xdr, const struct nsm_args *argp) { - resp->status = ntohl(*p++); - resp->state = ntohl(*p++); - dprintk("nsm: xdr_decode_stat_res status %d state %d\n", - resp->status, resp->state); + encode_mon_name(xdr, argp); + encode_my_id(xdr, argp); +} + +/* + * The "priv" argument may contain private information required + * by the NSMPROC_MON call. This information will be supplied in the + * NLMPROC_SM_NOTIFY call. + */ +static void encode_priv(struct xdr_stream *xdr, const struct nsm_args *argp) +{ + __be32 *p; + + p = xdr_reserve_space(xdr, SM_PRIV_SIZE); + xdr_encode_opaque_fixed(p, argp->priv->data, SM_PRIV_SIZE); +} + +static void nsm_xdr_enc_mon(struct rpc_rqst *req, struct xdr_stream *xdr, + const struct nsm_args *argp) +{ + encode_mon_id(xdr, argp); + encode_priv(xdr, argp); +} + +static void nsm_xdr_enc_unmon(struct rpc_rqst *req, struct xdr_stream *xdr, + const struct nsm_args *argp) +{ + encode_mon_id(xdr, argp); +} + +static int nsm_xdr_dec_stat_res(struct rpc_rqst *rqstp, + struct xdr_stream *xdr, + struct nsm_res *resp) +{ + __be32 *p; + + p = xdr_inline_decode(xdr, 4 + 4); + if (unlikely(p == NULL)) + return -EIO; + resp->status = be32_to_cpup(p++); + resp->state = be32_to_cpup(p); + + dprintk("lockd: %s status %d state %d\n", + __func__, resp->status, resp->state); return 0; } -static int -xdr_decode_stat(struct rpc_rqst *rqstp, u32 *p, struct nsm_res *resp) +static int nsm_xdr_dec_stat(struct rpc_rqst *rqstp, + struct xdr_stream *xdr, + struct nsm_res *resp) { - resp->state = ntohl(*p++); + __be32 *p; + + p = xdr_inline_decode(xdr, 4); + if (unlikely(p == NULL)) + return -EIO; + resp->state = be32_to_cpup(p); + + dprintk("lockd: %s state %d\n", __func__, resp->state); return 0; } #define SM_my_name_sz (1+XDR_QUADLEN(SM_MAXSTRLEN)) -#define SM_my_id_sz (3+1+SM_my_name_sz) -#define SM_mon_id_sz (1+XDR_QUADLEN(20)+SM_my_id_sz) -#define SM_mon_sz (SM_mon_id_sz+4) +#define SM_my_id_sz (SM_my_name_sz+3) +#define SM_mon_name_sz (1+XDR_QUADLEN(SM_MAXSTRLEN)) +#define SM_mon_id_sz (SM_mon_name_sz+SM_my_id_sz) +#define SM_priv_sz (XDR_QUADLEN(SM_PRIV_SIZE)) +#define SM_mon_sz (SM_mon_id_sz+SM_priv_sz) #define SM_monres_sz 2 #define SM_unmonres_sz 1 -#ifndef MAX -# define MAX(a, b) (((a) > (b))? (a) : (b)) -#endif - static struct rpc_procinfo nsm_procedures[] = { -[SM_MON] = { - .p_proc = SM_MON, - .p_encode = (kxdrproc_t) xdr_encode_mon, - .p_decode = (kxdrproc_t) xdr_decode_stat_res, - .p_bufsiz = MAX(SM_mon_sz, SM_monres_sz) << 2, +[NSMPROC_MON] = { + .p_proc = NSMPROC_MON, + .p_encode = (kxdreproc_t)nsm_xdr_enc_mon, + .p_decode = (kxdrdproc_t)nsm_xdr_dec_stat_res, + .p_arglen = SM_mon_sz, + .p_replen = SM_monres_sz, + .p_statidx = NSMPROC_MON, + .p_name = "MONITOR", }, -[SM_UNMON] = { - .p_proc = SM_UNMON, - .p_encode = (kxdrproc_t) xdr_encode_unmon, - .p_decode = (kxdrproc_t) xdr_decode_stat, - .p_bufsiz = MAX(SM_mon_id_sz, SM_unmonres_sz) << 2, +[NSMPROC_UNMON] = { + .p_proc = NSMPROC_UNMON, + .p_encode = (kxdreproc_t)nsm_xdr_enc_unmon, + .p_decode = (kxdrdproc_t)nsm_xdr_dec_stat, + .p_arglen = SM_mon_id_sz, + .p_replen = SM_unmonres_sz, + .p_statidx = NSMPROC_UNMON, + .p_name = "UNMONITOR", }, }; -static struct rpc_version nsm_version1 = { - .number = 1, - .nrprocs = sizeof(nsm_procedures)/sizeof(nsm_procedures[0]), +static const struct rpc_version nsm_version1 = { + .number = 1, + .nrprocs = ARRAY_SIZE(nsm_procedures), .procs = nsm_procedures }; -static struct rpc_version * nsm_version[] = { +static const struct rpc_version *nsm_version[] = { [1] = &nsm_version1, }; static struct rpc_stat nsm_stats; -static struct rpc_program nsm_program = { +static const struct rpc_program nsm_program = { .name = "statd", - .number = SM_PROGRAM, - .nrvers = sizeof(nsm_version)/sizeof(nsm_version[0]), + .number = NSM_PROGRAM, + .nrvers = ARRAY_SIZE(nsm_version), .version = nsm_version, .stats = &nsm_stats }; diff --git a/fs/lockd/netns.h b/fs/lockd/netns.h new file mode 100644 index 00000000000..5010b55628b --- /dev/null +++ b/fs/lockd/netns.h @@ -0,0 +1,23 @@ +#ifndef __LOCKD_NETNS_H__ +#define __LOCKD_NETNS_H__ + +#include <linux/fs.h> +#include <net/netns/generic.h> + +struct lockd_net { + unsigned int nlmsvc_users; + unsigned long next_gc; + unsigned long nrhosts; + + struct delayed_work grace_period_end; + struct lock_manager lockd_manager; + struct list_head grace_list; + + spinlock_t nsm_clnt_lock; + unsigned int nsm_users; + struct rpc_clnt *nsm_clnt; +}; + +extern int lockd_net_id; + +#endif diff --git a/fs/lockd/svc.c b/fs/lockd/svc.c index 71a30b416d1..8f27c93f8d2 100644 --- a/fs/lockd/svc.c +++ b/fs/lockd/svc.c @@ -12,7 +12,6 @@ * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> */ -#include <linux/config.h> #include <linux/module.h> #include <linux/init.h> #include <linux/sysctl.h> @@ -22,18 +21,22 @@ #include <linux/errno.h> #include <linux/in.h> #include <linux/uio.h> -#include <linux/slab.h> #include <linux/smp.h> -#include <linux/smp_lock.h> +#include <linux/mutex.h> +#include <linux/kthread.h> +#include <linux/freezer.h> #include <linux/sunrpc/types.h> #include <linux/sunrpc/stats.h> #include <linux/sunrpc/clnt.h> #include <linux/sunrpc/svc.h> #include <linux/sunrpc/svcsock.h> +#include <net/ip.h> #include <linux/lockd/lockd.h> #include <linux/nfs.h> +#include "netns.h" + #define NLMDBG_FACILITY NLMDBG_SVC #define LOCKD_BUFSIZE (1024 + NLMSVC_XDRSIZE) #define ALLOWED_SIGS (sigmask(SIGKILL)) @@ -41,16 +44,15 @@ static struct svc_program nlmsvc_program; struct nlmsvc_binding * nlmsvc_ops; -EXPORT_SYMBOL(nlmsvc_ops); +EXPORT_SYMBOL_GPL(nlmsvc_ops); -static DECLARE_MUTEX(nlmsvc_sema); +static DEFINE_MUTEX(nlmsvc_mutex); static unsigned int nlmsvc_users; -static pid_t nlmsvc_pid; -int nlmsvc_grace_period; +static struct task_struct *nlmsvc_task; +static struct svc_rqst *nlmsvc_rqst; unsigned long nlmsvc_timeout; -static DECLARE_MUTEX_LOCKED(lockd_start); -static DECLARE_WAIT_QUEUE_HEAD(lockd_exit); +int lockd_net_id; /* * These can be set at insmod time (useful for NFS as root filesystem), @@ -60,6 +62,9 @@ static unsigned long nlm_grace_period; static unsigned long nlm_timeout = LOCKD_DFLT_TIMEO; static int nlm_udpport, nlm_tcpport; +/* RLIM_NOFILE defaults to 1024. That seems like a reasonable default here. */ +static unsigned int nlm_max_connections = 1024; + /* * Constants needed for the sysctl interface. */ @@ -69,329 +74,444 @@ static const unsigned long nlm_timeout_min = 3; static const unsigned long nlm_timeout_max = 20; static const int nlm_port_min = 0, nlm_port_max = 65535; +#ifdef CONFIG_SYSCTL static struct ctl_table_header * nlm_sysctl_table; +#endif -static unsigned long set_grace_period(void) +static unsigned long get_lockd_grace_period(void) { - unsigned long grace_period; - /* Note: nlm_timeout should always be nonzero */ if (nlm_grace_period) - grace_period = ((nlm_grace_period + nlm_timeout - 1) - / nlm_timeout) * nlm_timeout * HZ; + return roundup(nlm_grace_period, nlm_timeout) * HZ; else - grace_period = nlm_timeout * 5 * HZ; - nlmsvc_grace_period = 1; - return grace_period + jiffies; + return nlm_timeout * 5 * HZ; +} + +static void grace_ender(struct work_struct *grace) +{ + struct delayed_work *dwork = container_of(grace, struct delayed_work, + work); + struct lockd_net *ln = container_of(dwork, struct lockd_net, + grace_period_end); + + locks_end_grace(&ln->lockd_manager); } -static inline void clear_grace_period(void) +static void set_grace_period(struct net *net) { - nlmsvc_grace_period = 0; + unsigned long grace_period = get_lockd_grace_period(); + struct lockd_net *ln = net_generic(net, lockd_net_id); + + locks_start_grace(net, &ln->lockd_manager); + cancel_delayed_work_sync(&ln->grace_period_end); + schedule_delayed_work(&ln->grace_period_end, grace_period); +} + +static void restart_grace(void) +{ + if (nlmsvc_ops) { + struct net *net = &init_net; + struct lockd_net *ln = net_generic(net, lockd_net_id); + + cancel_delayed_work_sync(&ln->grace_period_end); + locks_end_grace(&ln->lockd_manager); + nlmsvc_invalidate_all(); + set_grace_period(net); + } } /* * This is the lockd kernel thread */ -static void -lockd(struct svc_rqst *rqstp) +static int +lockd(void *vrqstp) { - struct svc_serv *serv = rqstp->rq_server; int err = 0; - unsigned long grace_period_expire; + struct svc_rqst *rqstp = vrqstp; - /* Lock module and set up kernel thread */ - /* lockd_up is waiting for us to startup, so will - * be holding a reference to this module, so it - * is safe to just claim another reference - */ - __module_get(THIS_MODULE); - lock_kernel(); + /* try_to_freeze() is called from svc_recv() */ + set_freezable(); - /* - * Let our maker know we're running. - */ - nlmsvc_pid = current->pid; - up(&lockd_start); - - daemonize("lockd"); - - /* Process request with signals blocked, but allow SIGKILL. */ + /* Allow SIGKILL to tell lockd to drop all of its locks */ allow_signal(SIGKILL); - /* kick rpciod */ - rpciod_up(); - dprintk("NFS locking service started (ver " LOCKD_VERSION ").\n"); if (!nlm_timeout) nlm_timeout = LOCKD_DFLT_TIMEO; nlmsvc_timeout = nlm_timeout * HZ; - grace_period_expire = set_grace_period(); - /* * The main request loop. We don't terminate until the last - * NFS mount or NFS daemon has gone away, and we've been sent a - * signal, or else another process has taken over our job. + * NFS mount or NFS daemon has gone away. */ - while ((nlmsvc_users || !signalled()) && nlmsvc_pid == current->pid) { + while (!kthread_should_stop()) { long timeout = MAX_SCHEDULE_TIMEOUT; + RPC_IFDEBUG(char buf[RPC_MAX_ADDRBUFLEN]); + + /* update sv_maxconn if it has changed */ + rqstp->rq_server->sv_maxconn = nlm_max_connections; if (signalled()) { flush_signals(current); - if (nlmsvc_ops) { - nlmsvc_invalidate_all(); - grace_period_expire = set_grace_period(); - } + restart_grace(); + continue; } - /* - * Retry any blocked locks that have been notified by - * the VFS. Don't do this during grace period. - * (Theoretically, there shouldn't even be blocked locks - * during grace period). - */ - if (!nlmsvc_grace_period) { - timeout = nlmsvc_retry_blocked(); - } else if (time_before(grace_period_expire, jiffies)) - clear_grace_period(); + timeout = nlmsvc_retry_blocked(); /* * Find a socket with data available and call its * recvfrom routine. */ - err = svc_recv(serv, rqstp, timeout); + err = svc_recv(rqstp, timeout); if (err == -EAGAIN || err == -EINTR) continue; - if (err < 0) { - printk(KERN_WARNING - "lockd: terminating on error %d\n", - -err); - break; - } + dprintk("lockd: request from %s\n", + svc_print_addr(rqstp, buf, sizeof(buf))); - dprintk("lockd: request from %08x\n", - (unsigned)ntohl(rqstp->rq_addr.sin_addr.s_addr)); + svc_process(rqstp); + } + flush_signals(current); + if (nlmsvc_ops) + nlmsvc_invalidate_all(); + nlm_shutdown_hosts(); + return 0; +} - svc_process(serv, rqstp); +static int create_lockd_listener(struct svc_serv *serv, const char *name, + struct net *net, const int family, + const unsigned short port) +{ + struct svc_xprt *xprt; + + xprt = svc_find_xprt(serv, name, net, family, 0); + if (xprt == NULL) + return svc_create_xprt(serv, name, net, family, port, + SVC_SOCK_DEFAULTS); + svc_xprt_put(xprt); + return 0; +} - } +static int create_lockd_family(struct svc_serv *serv, struct net *net, + const int family) +{ + int err; - flush_signals(current); + err = create_lockd_listener(serv, "udp", net, family, nlm_udpport); + if (err < 0) + return err; - /* - * Check whether there's a new lockd process before - * shutting down the hosts and clearing the slot. - */ - if (!nlmsvc_pid || current->pid == nlmsvc_pid) { - if (nlmsvc_ops) - nlmsvc_invalidate_all(); - nlm_shutdown_hosts(); - nlmsvc_pid = 0; - } else - printk(KERN_DEBUG - "lockd: new process, skipping host shutdown\n"); - wake_up(&lockd_exit); - - /* Exit the RPC thread */ - svc_exit_thread(rqstp); - - /* release rpciod */ - rpciod_down(); - - /* Release module */ - unlock_kernel(); - module_put_and_exit(0); + return create_lockd_listener(serv, "tcp", net, family, nlm_tcpport); } /* - * Bring up the lockd process if it's not already up. + * Ensure there are active UDP and TCP listeners for lockd. + * + * Even if we have only TCP NFS mounts and/or TCP NFSDs, some + * local services (such as rpc.statd) still require UDP, and + * some NFS servers do not yet support NLM over TCP. + * + * Returns zero if all listeners are available; otherwise a + * negative errno value is returned. */ -int -lockd_up(void) +static int make_socks(struct svc_serv *serv, struct net *net) { - static int warned; - struct svc_serv * serv; - int error = 0; + static int warned; + int err; + + err = create_lockd_family(serv, net, PF_INET); + if (err < 0) + goto out_err; + + err = create_lockd_family(serv, net, PF_INET6); + if (err < 0 && err != -EAFNOSUPPORT) + goto out_err; + + warned = 0; + return 0; + +out_err: + if (warned++ == 0) + printk(KERN_WARNING + "lockd_up: makesock failed, error=%d\n", err); + svc_shutdown_net(serv, net); + return err; +} + +static int lockd_up_net(struct svc_serv *serv, struct net *net) +{ + struct lockd_net *ln = net_generic(net, lockd_net_id); + int error; + + if (ln->nlmsvc_users++) + return 0; + + error = svc_bind(serv, net); + if (error) + goto err_bind; + + error = make_socks(serv, net); + if (error < 0) + goto err_socks; + set_grace_period(net); + dprintk("lockd_up_net: per-net data created; net=%p\n", net); + return 0; + +err_socks: + svc_rpcb_cleanup(serv, net); +err_bind: + ln->nlmsvc_users--; + return error; +} + +static void lockd_down_net(struct svc_serv *serv, struct net *net) +{ + struct lockd_net *ln = net_generic(net, lockd_net_id); + + if (ln->nlmsvc_users) { + if (--ln->nlmsvc_users == 0) { + nlm_shutdown_hosts_net(net); + cancel_delayed_work_sync(&ln->grace_period_end); + locks_end_grace(&ln->lockd_manager); + svc_shutdown_net(serv, net); + dprintk("lockd_down_net: per-net data destroyed; net=%p\n", net); + } + } else { + printk(KERN_ERR "lockd_down_net: no users! task=%p, net=%p\n", + nlmsvc_task, net); + BUG(); + } +} + +static int lockd_start_svc(struct svc_serv *serv) +{ + int error; + + if (nlmsvc_rqst) + return 0; - down(&nlmsvc_sema); /* - * Unconditionally increment the user count ... this is - * the number of clients who _want_ a lockd process. + * Create the kernel thread and wait for it to start. */ - nlmsvc_users++; + nlmsvc_rqst = svc_prepare_thread(serv, &serv->sv_pools[0], NUMA_NO_NODE); + if (IS_ERR(nlmsvc_rqst)) { + error = PTR_ERR(nlmsvc_rqst); + printk(KERN_WARNING + "lockd_up: svc_rqst allocation failed, error=%d\n", + error); + goto out_rqst; + } + + svc_sock_update_bufs(serv); + serv->sv_maxconn = nlm_max_connections; + + nlmsvc_task = kthread_run(lockd, nlmsvc_rqst, "%s", serv->sv_name); + if (IS_ERR(nlmsvc_task)) { + error = PTR_ERR(nlmsvc_task); + printk(KERN_WARNING + "lockd_up: kthread_run failed, error=%d\n", error); + goto out_task; + } + dprintk("lockd_up: service started\n"); + return 0; + +out_task: + svc_exit_thread(nlmsvc_rqst); + nlmsvc_task = NULL; +out_rqst: + nlmsvc_rqst = NULL; + return error; +} + +static struct svc_serv *lockd_create_svc(void) +{ + struct svc_serv *serv; + /* * Check whether we're already up and running. */ - if (nlmsvc_pid) - goto out; + if (nlmsvc_rqst) { + /* + * Note: increase service usage, because later in case of error + * svc_destroy() will be called. + */ + svc_get(nlmsvc_rqst->rq_server); + return nlmsvc_rqst->rq_server; + } /* * Sanity check: if there's no pid, * we should be the first user ... */ - if (nlmsvc_users > 1) + if (nlmsvc_users) printk(KERN_WARNING "lockd_up: no pid, %d users??\n", nlmsvc_users); - error = -ENOMEM; - serv = svc_create(&nlmsvc_program, LOCKD_BUFSIZE); + serv = svc_create(&nlmsvc_program, LOCKD_BUFSIZE, NULL); if (!serv) { printk(KERN_WARNING "lockd_up: create service failed\n"); - goto out; + return ERR_PTR(-ENOMEM); } + dprintk("lockd_up: service created\n"); + return serv; +} - if ((error = svc_makesock(serv, IPPROTO_UDP, nlm_udpport)) < 0 -#ifdef CONFIG_NFSD_TCP - || (error = svc_makesock(serv, IPPROTO_TCP, nlm_tcpport)) < 0 -#endif - ) { - if (warned++ == 0) - printk(KERN_WARNING - "lockd_up: makesock failed, error=%d\n", error); - goto destroy_and_out; - } - warned = 0; +/* + * Bring up the lockd process if it's not already up. + */ +int lockd_up(struct net *net) +{ + struct svc_serv *serv; + int error; - /* - * Create the kernel thread and wait for it to start. - */ - error = svc_create_thread(lockd, serv); - if (error) { - printk(KERN_WARNING - "lockd_up: create thread failed, error=%d\n", error); - goto destroy_and_out; + mutex_lock(&nlmsvc_mutex); + + serv = lockd_create_svc(); + if (IS_ERR(serv)) { + error = PTR_ERR(serv); + goto err_create; } - down(&lockd_start); + error = lockd_up_net(serv, net); + if (error < 0) + goto err_net; + + error = lockd_start_svc(serv); + if (error < 0) + goto err_start; + + nlmsvc_users++; /* * Note: svc_serv structures have an initial use count of 1, * so we exit through here on both success and failure. */ -destroy_and_out: +err_net: svc_destroy(serv); -out: - up(&nlmsvc_sema); +err_create: + mutex_unlock(&nlmsvc_mutex); return error; + +err_start: + lockd_down_net(serv, net); + goto err_net; } -EXPORT_SYMBOL(lockd_up); +EXPORT_SYMBOL_GPL(lockd_up); /* * Decrement the user count and bring down lockd if we're the last. */ void -lockd_down(void) +lockd_down(struct net *net) { - static int warned; - - down(&nlmsvc_sema); + mutex_lock(&nlmsvc_mutex); + lockd_down_net(nlmsvc_rqst->rq_server, net); if (nlmsvc_users) { if (--nlmsvc_users) goto out; - } else - printk(KERN_WARNING "lockd_down: no users! pid=%d\n", nlmsvc_pid); - - if (!nlmsvc_pid) { - if (warned++ == 0) - printk(KERN_WARNING "lockd_down: no lockd running.\n"); - goto out; + } else { + printk(KERN_ERR "lockd_down: no users! task=%p\n", + nlmsvc_task); + BUG(); } - warned = 0; - kill_proc(nlmsvc_pid, SIGKILL, 1); - /* - * Wait for the lockd process to exit, but since we're holding - * the lockd semaphore, we can't wait around forever ... - */ - clear_thread_flag(TIF_SIGPENDING); - interruptible_sleep_on_timeout(&lockd_exit, HZ); - if (nlmsvc_pid) { - printk(KERN_WARNING - "lockd_down: lockd failed to exit, clearing pid\n"); - nlmsvc_pid = 0; + if (!nlmsvc_task) { + printk(KERN_ERR "lockd_down: no lockd running.\n"); + BUG(); } - spin_lock_irq(¤t->sighand->siglock); - recalc_sigpending(); - spin_unlock_irq(¤t->sighand->siglock); + kthread_stop(nlmsvc_task); + dprintk("lockd_down: service stopped\n"); + svc_exit_thread(nlmsvc_rqst); + dprintk("lockd_down: service destroyed\n"); + nlmsvc_task = NULL; + nlmsvc_rqst = NULL; out: - up(&nlmsvc_sema); + mutex_unlock(&nlmsvc_mutex); } -EXPORT_SYMBOL(lockd_down); +EXPORT_SYMBOL_GPL(lockd_down); + +#ifdef CONFIG_SYSCTL /* * Sysctl parameters (same as module parameters, different interface). */ -/* Something that isn't CTL_ANY, CTL_NONE or a value that may clash. */ -#define CTL_UNNUMBERED -2 - -static ctl_table nlm_sysctls[] = { +static struct ctl_table nlm_sysctls[] = { { - .ctl_name = CTL_UNNUMBERED, .procname = "nlm_grace_period", .data = &nlm_grace_period, .maxlen = sizeof(unsigned long), .mode = 0644, - .proc_handler = &proc_doulongvec_minmax, + .proc_handler = proc_doulongvec_minmax, .extra1 = (unsigned long *) &nlm_grace_period_min, .extra2 = (unsigned long *) &nlm_grace_period_max, }, { - .ctl_name = CTL_UNNUMBERED, .procname = "nlm_timeout", .data = &nlm_timeout, .maxlen = sizeof(unsigned long), .mode = 0644, - .proc_handler = &proc_doulongvec_minmax, + .proc_handler = proc_doulongvec_minmax, .extra1 = (unsigned long *) &nlm_timeout_min, .extra2 = (unsigned long *) &nlm_timeout_max, }, { - .ctl_name = CTL_UNNUMBERED, .procname = "nlm_udpport", .data = &nlm_udpport, .maxlen = sizeof(int), .mode = 0644, - .proc_handler = &proc_dointvec_minmax, + .proc_handler = proc_dointvec_minmax, .extra1 = (int *) &nlm_port_min, .extra2 = (int *) &nlm_port_max, }, { - .ctl_name = CTL_UNNUMBERED, .procname = "nlm_tcpport", .data = &nlm_tcpport, .maxlen = sizeof(int), .mode = 0644, - .proc_handler = &proc_dointvec_minmax, + .proc_handler = proc_dointvec_minmax, .extra1 = (int *) &nlm_port_min, .extra2 = (int *) &nlm_port_max, }, - { .ctl_name = 0 } + { + .procname = "nsm_use_hostnames", + .data = &nsm_use_hostnames, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { + .procname = "nsm_local_state", + .data = &nsm_local_state, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec, + }, + { } }; -static ctl_table nlm_sysctl_dir[] = { +static struct ctl_table nlm_sysctl_dir[] = { { - .ctl_name = CTL_UNNUMBERED, .procname = "nfs", .mode = 0555, .child = nlm_sysctls, }, - { .ctl_name = 0 } + { } }; -static ctl_table nlm_sysctl_root[] = { +static struct ctl_table nlm_sysctl_root[] = { { - .ctl_name = CTL_FS, .procname = "fs", .mode = 0555, .child = nlm_sysctl_dir, }, - { .ctl_name = 0 } + { } }; +#endif /* CONFIG_SYSCTL */ + /* - * Module (and driverfs) parameters. + * Module (and sysfs) parameters. */ #define param_set_min_max(name, type, which_strtol, min, max) \ @@ -401,7 +521,7 @@ static int param_set_##name(const char *val, struct kernel_param *kp) \ __typeof__(type) num = which_strtol(val, &endp, 0); \ if (endp == val || *endp || num < (min) || num > (max)) \ return -EINVAL; \ - *((int *) kp->arg) = num; \ + *((type *) kp->arg) = num; \ return 0; \ } @@ -455,6 +575,30 @@ module_param_call(nlm_udpport, param_set_port, param_get_int, &nlm_udpport, 0644); module_param_call(nlm_tcpport, param_set_port, param_get_int, &nlm_tcpport, 0644); +module_param(nsm_use_hostnames, bool, 0644); +module_param(nlm_max_connections, uint, 0644); + +static int lockd_init_net(struct net *net) +{ + struct lockd_net *ln = net_generic(net, lockd_net_id); + + INIT_DELAYED_WORK(&ln->grace_period_end, grace_ender); + INIT_LIST_HEAD(&ln->grace_list); + spin_lock_init(&ln->nsm_clnt_lock); + return 0; +} + +static void lockd_exit_net(struct net *net) +{ +} + +static struct pernet_operations lockd_net_ops = { + .init = lockd_init_net, + .exit = lockd_exit_net, + .id = &lockd_net_id, + .size = sizeof(struct lockd_net), +}; + /* * Initialising and terminating the module. @@ -462,15 +606,35 @@ module_param_call(nlm_tcpport, param_set_port, param_get_int, static int __init init_nlm(void) { - nlm_sysctl_table = register_sysctl_table(nlm_sysctl_root, 0); - return nlm_sysctl_table ? 0 : -ENOMEM; + int err; + +#ifdef CONFIG_SYSCTL + err = -ENOMEM; + nlm_sysctl_table = register_sysctl_table(nlm_sysctl_root); + if (nlm_sysctl_table == NULL) + goto err_sysctl; +#endif + err = register_pernet_subsys(&lockd_net_ops); + if (err) + goto err_pernet; + return 0; + +err_pernet: +#ifdef CONFIG_SYSCTL + unregister_sysctl_table(nlm_sysctl_table); +err_sysctl: +#endif + return err; } static void __exit exit_nlm(void) { /* FIXME: delete all NLM clients */ nlm_shutdown_hosts(); + unregister_pernet_subsys(&lockd_net_ops); +#ifdef CONFIG_SYSCTL unregister_sysctl_table(nlm_sysctl_table); +#endif } module_init(init_nlm); @@ -509,7 +673,7 @@ static struct svc_version * nlmsvc_version[] = { static struct svc_stat nlmsvc_stats; -#define NLM_NRVERS (sizeof(nlmsvc_version)/sizeof(nlmsvc_version[0])) +#define NLM_NRVERS ARRAY_SIZE(nlmsvc_version) static struct svc_program nlmsvc_program = { .pg_prog = NLM_PROGRAM, /* program number */ .pg_nvers = NLM_NRVERS, /* number of entries in nlmsvc_version */ diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c index 4063095d849..b147d1ae71f 100644 --- a/fs/lockd/svc4proc.c +++ b/fs/lockd/svc4proc.c @@ -9,41 +9,31 @@ #include <linux/types.h> #include <linux/time.h> -#include <linux/slab.h> -#include <linux/in.h> -#include <linux/sunrpc/svc.h> -#include <linux/sunrpc/clnt.h> -#include <linux/nfsd/nfsd.h> #include <linux/lockd/lockd.h> #include <linux/lockd/share.h> -#include <linux/lockd/sm_inter.h> - +#include <linux/sunrpc/svc_xprt.h> #define NLMDBG_FACILITY NLMDBG_CLIENT -static u32 nlm4svc_callback(struct svc_rqst *, u32, struct nlm_res *); - -static const struct rpc_call_ops nlm4svc_callback_ops; - /* * Obtain client and file from arguments */ -static u32 +static __be32 nlm4svc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_host **hostp, struct nlm_file **filp) { struct nlm_host *host = NULL; struct nlm_file *file = NULL; struct nlm_lock *lock = &argp->lock; - u32 error = 0; + __be32 error = 0; /* nfsd callbacks must have been installed for this procedure */ if (!nlmsvc_ops) return nlm_lck_denied_nolocks; /* Obtain host handle */ - if (!(host = nlmsvc_lookup_host(rqstp)) - || (argp->monitor && !host->h_monitored && nsm_monitor(host) < 0)) + if (!(host = nlmsvc_lookup_host(rqstp, lock->caller, lock->len)) + || (argp->monitor && nsm_monitor(host) < 0)) goto no_locks; *hostp = host; @@ -62,8 +52,7 @@ nlm4svc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp, return 0; no_locks: - if (host) - nlm_release_host(host); + nlmsvc_release_host(host); if (error) return error; return nlm_lck_denied_nolocks; @@ -72,7 +61,7 @@ no_locks: /* * NULL: Test for presence of service */ -static int +static __be32 nlm4svc_proc_null(struct svc_rqst *rqstp, void *argp, void *resp) { dprintk("lockd: NULL called\n"); @@ -82,55 +71,48 @@ nlm4svc_proc_null(struct svc_rqst *rqstp, void *argp, void *resp) /* * TEST: Check for conflicting lock */ -static int +static __be32 nlm4svc_proc_test(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { struct nlm_host *host; struct nlm_file *file; + __be32 rc = rpc_success; dprintk("lockd: TEST4 called\n"); resp->cookie = argp->cookie; - /* Don't accept test requests during grace period */ - if (nlmsvc_grace_period) { - resp->status = nlm_lck_denied_grace_period; - return rpc_success; - } - /* Obtain client and file */ if ((resp->status = nlm4svc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; /* Now check for conflicting locks */ - resp->status = nlmsvc_testlock(file, &argp->lock, &resp->lock); + resp->status = nlmsvc_testlock(rqstp, file, host, &argp->lock, &resp->lock, &resp->cookie); + if (resp->status == nlm_drop_reply) + rc = rpc_drop_reply; + else + dprintk("lockd: TEST4 status %d\n", ntohl(resp->status)); - dprintk("lockd: TEST4 status %d\n", ntohl(resp->status)); - nlm_release_host(host); + nlmsvc_release_host(host); nlm_release_file(file); - return rpc_success; + return rc; } -static int +static __be32 nlm4svc_proc_lock(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { struct nlm_host *host; struct nlm_file *file; + __be32 rc = rpc_success; dprintk("lockd: LOCK called\n"); resp->cookie = argp->cookie; - /* Don't accept new lock requests during grace period */ - if (nlmsvc_grace_period && !argp->reclaim) { - resp->status = nlm_lck_denied_grace_period; - return rpc_success; - } - /* Obtain client and file */ if ((resp->status = nlm4svc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; #if 0 /* If supplied state doesn't match current state, we assume it's @@ -145,16 +127,20 @@ nlm4svc_proc_lock(struct svc_rqst *rqstp, struct nlm_args *argp, #endif /* Now try to lock the file */ - resp->status = nlmsvc_lock(rqstp, file, &argp->lock, - argp->block, &argp->cookie); - - dprintk("lockd: LOCK status %d\n", ntohl(resp->status)); - nlm_release_host(host); + resp->status = nlmsvc_lock(rqstp, file, host, &argp->lock, + argp->block, &argp->cookie, + argp->reclaim); + if (resp->status == nlm_drop_reply) + rc = rpc_drop_reply; + else + dprintk("lockd: LOCK status %d\n", ntohl(resp->status)); + + nlmsvc_release_host(host); nlm_release_file(file); - return rpc_success; + return rc; } -static int +static __be32 nlm4svc_proc_cancel(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { @@ -166,20 +152,20 @@ nlm4svc_proc_cancel(struct svc_rqst *rqstp, struct nlm_args *argp, resp->cookie = argp->cookie; /* Don't accept requests during grace period */ - if (nlmsvc_grace_period) { + if (locks_in_grace(SVC_NET(rqstp))) { resp->status = nlm_lck_denied_grace_period; return rpc_success; } /* Obtain client and file */ if ((resp->status = nlm4svc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; /* Try to cancel request. */ - resp->status = nlmsvc_cancel_blocked(file, &argp->lock); + resp->status = nlmsvc_cancel_blocked(SVC_NET(rqstp), file, &argp->lock); dprintk("lockd: CANCEL status %d\n", ntohl(resp->status)); - nlm_release_host(host); + nlmsvc_release_host(host); nlm_release_file(file); return rpc_success; } @@ -187,7 +173,7 @@ nlm4svc_proc_cancel(struct svc_rqst *rqstp, struct nlm_args *argp, /* * UNLOCK: release a lock */ -static int +static __be32 nlm4svc_proc_unlock(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { @@ -199,20 +185,20 @@ nlm4svc_proc_unlock(struct svc_rqst *rqstp, struct nlm_args *argp, resp->cookie = argp->cookie; /* Don't accept new lock requests during grace period */ - if (nlmsvc_grace_period) { + if (locks_in_grace(SVC_NET(rqstp))) { resp->status = nlm_lck_denied_grace_period; return rpc_success; } /* Obtain client and file */ if ((resp->status = nlm4svc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; /* Now try to remove the lock */ - resp->status = nlmsvc_unlock(file, &argp->lock); + resp->status = nlmsvc_unlock(SVC_NET(rqstp), file, &argp->lock); dprintk("lockd: UNLOCK status %d\n", ntohl(resp->status)); - nlm_release_host(host); + nlmsvc_release_host(host); nlm_release_file(file); return rpc_success; } @@ -221,102 +207,111 @@ nlm4svc_proc_unlock(struct svc_rqst *rqstp, struct nlm_args *argp, * GRANTED: A server calls us to tell that a process' lock request * was granted */ -static int +static __be32 nlm4svc_proc_granted(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { resp->cookie = argp->cookie; dprintk("lockd: GRANTED called\n"); - resp->status = nlmclnt_grant(&argp->lock); + resp->status = nlmclnt_grant(svc_addr(rqstp), &argp->lock); dprintk("lockd: GRANTED status %d\n", ntohl(resp->status)); return rpc_success; } /* + * This is the generic lockd callback for async RPC calls + */ +static void nlm4svc_callback_exit(struct rpc_task *task, void *data) +{ + dprintk("lockd: %5u callback returned %d\n", task->tk_pid, + -task->tk_status); +} + +static void nlm4svc_callback_release(void *data) +{ + nlmsvc_release_call(data); +} + +static const struct rpc_call_ops nlm4svc_callback_ops = { + .rpc_call_done = nlm4svc_callback_exit, + .rpc_release = nlm4svc_callback_release, +}; + +/* * `Async' versions of the above service routines. They aren't really, * because we send the callback before the reply proper. I hope this * doesn't break any clients. */ -static int -nlm4svc_proc_test_msg(struct svc_rqst *rqstp, struct nlm_args *argp, - void *resp) +static __be32 nlm4svc_callback(struct svc_rqst *rqstp, u32 proc, struct nlm_args *argp, + __be32 (*func)(struct svc_rqst *, struct nlm_args *, struct nlm_res *)) { - struct nlm_res res; - u32 stat; + struct nlm_host *host; + struct nlm_rqst *call; + __be32 stat; - dprintk("lockd: TEST_MSG called\n"); - memset(&res, 0, sizeof(res)); + host = nlmsvc_lookup_host(rqstp, + argp->lock.caller, + argp->lock.len); + if (host == NULL) + return rpc_system_err; - if ((stat = nlm4svc_proc_test(rqstp, argp, &res)) == 0) - stat = nlm4svc_callback(rqstp, NLMPROC_TEST_RES, &res); - return stat; + call = nlm_alloc_call(host); + nlmsvc_release_host(host); + if (call == NULL) + return rpc_system_err; + + stat = func(rqstp, argp, &call->a_res); + if (stat != 0) { + nlmsvc_release_call(call); + return stat; + } + + call->a_flags = RPC_TASK_ASYNC; + if (nlm_async_reply(call, proc, &nlm4svc_callback_ops) < 0) + return rpc_system_err; + return rpc_success; } -static int -nlm4svc_proc_lock_msg(struct svc_rqst *rqstp, struct nlm_args *argp, +static __be32 nlm4svc_proc_test_msg(struct svc_rqst *rqstp, struct nlm_args *argp, void *resp) { - struct nlm_res res; - u32 stat; + dprintk("lockd: TEST_MSG called\n"); + return nlm4svc_callback(rqstp, NLMPROC_TEST_RES, argp, nlm4svc_proc_test); +} +static __be32 nlm4svc_proc_lock_msg(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ dprintk("lockd: LOCK_MSG called\n"); - memset(&res, 0, sizeof(res)); - - if ((stat = nlm4svc_proc_lock(rqstp, argp, &res)) == 0) - stat = nlm4svc_callback(rqstp, NLMPROC_LOCK_RES, &res); - return stat; + return nlm4svc_callback(rqstp, NLMPROC_LOCK_RES, argp, nlm4svc_proc_lock); } -static int -nlm4svc_proc_cancel_msg(struct svc_rqst *rqstp, struct nlm_args *argp, +static __be32 nlm4svc_proc_cancel_msg(struct svc_rqst *rqstp, struct nlm_args *argp, void *resp) { - struct nlm_res res; - u32 stat; - dprintk("lockd: CANCEL_MSG called\n"); - memset(&res, 0, sizeof(res)); - - if ((stat = nlm4svc_proc_cancel(rqstp, argp, &res)) == 0) - stat = nlm4svc_callback(rqstp, NLMPROC_CANCEL_RES, &res); - return stat; + return nlm4svc_callback(rqstp, NLMPROC_CANCEL_RES, argp, nlm4svc_proc_cancel); } -static int -nlm4svc_proc_unlock_msg(struct svc_rqst *rqstp, struct nlm_args *argp, +static __be32 nlm4svc_proc_unlock_msg(struct svc_rqst *rqstp, struct nlm_args *argp, void *resp) { - struct nlm_res res; - u32 stat; - dprintk("lockd: UNLOCK_MSG called\n"); - memset(&res, 0, sizeof(res)); - - if ((stat = nlm4svc_proc_unlock(rqstp, argp, &res)) == 0) - stat = nlm4svc_callback(rqstp, NLMPROC_UNLOCK_RES, &res); - return stat; + return nlm4svc_callback(rqstp, NLMPROC_UNLOCK_RES, argp, nlm4svc_proc_unlock); } -static int -nlm4svc_proc_granted_msg(struct svc_rqst *rqstp, struct nlm_args *argp, +static __be32 nlm4svc_proc_granted_msg(struct svc_rqst *rqstp, struct nlm_args *argp, void *resp) { - struct nlm_res res; - u32 stat; - dprintk("lockd: GRANTED_MSG called\n"); - memset(&res, 0, sizeof(res)); - - if ((stat = nlm4svc_proc_granted(rqstp, argp, &res)) == 0) - stat = nlm4svc_callback(rqstp, NLMPROC_GRANTED_RES, &res); - return stat; + return nlm4svc_callback(rqstp, NLMPROC_GRANTED_RES, argp, nlm4svc_proc_granted); } /* * SHARE: create a DOS share or alter existing share. */ -static int +static __be32 nlm4svc_proc_share(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { @@ -328,20 +323,20 @@ nlm4svc_proc_share(struct svc_rqst *rqstp, struct nlm_args *argp, resp->cookie = argp->cookie; /* Don't accept new lock requests during grace period */ - if (nlmsvc_grace_period && !argp->reclaim) { + if (locks_in_grace(SVC_NET(rqstp)) && !argp->reclaim) { resp->status = nlm_lck_denied_grace_period; return rpc_success; } /* Obtain client and file */ if ((resp->status = nlm4svc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; /* Now try to create the share */ resp->status = nlmsvc_share_file(host, file, argp); dprintk("lockd: SHARE status %d\n", ntohl(resp->status)); - nlm_release_host(host); + nlmsvc_release_host(host); nlm_release_file(file); return rpc_success; } @@ -349,7 +344,7 @@ nlm4svc_proc_share(struct svc_rqst *rqstp, struct nlm_args *argp, /* * UNSHARE: Release a DOS share. */ -static int +static __be32 nlm4svc_proc_unshare(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { @@ -361,20 +356,20 @@ nlm4svc_proc_unshare(struct svc_rqst *rqstp, struct nlm_args *argp, resp->cookie = argp->cookie; /* Don't accept requests during grace period */ - if (nlmsvc_grace_period) { + if (locks_in_grace(SVC_NET(rqstp))) { resp->status = nlm_lck_denied_grace_period; return rpc_success; } /* Obtain client and file */ if ((resp->status = nlm4svc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; /* Now try to lock the file */ resp->status = nlmsvc_unshare_file(host, file, argp); dprintk("lockd: UNSHARE status %d\n", ntohl(resp->status)); - nlm_release_host(host); + nlmsvc_release_host(host); nlm_release_file(file); return rpc_success; } @@ -382,7 +377,7 @@ nlm4svc_proc_unshare(struct svc_rqst *rqstp, struct nlm_args *argp, /* * NM_LOCK: Create an unmonitored lock */ -static int +static __be32 nlm4svc_proc_nm_lock(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { @@ -395,7 +390,7 @@ nlm4svc_proc_nm_lock(struct svc_rqst *rqstp, struct nlm_args *argp, /* * FREE_ALL: Release all locks and shares held by client */ -static int +static __be32 nlm4svc_proc_free_all(struct svc_rqst *rqstp, struct nlm_args *argp, void *resp) { @@ -406,58 +401,34 @@ nlm4svc_proc_free_all(struct svc_rqst *rqstp, struct nlm_args *argp, return rpc_success; nlmsvc_free_host_resources(host); - nlm_release_host(host); + nlmsvc_release_host(host); return rpc_success; } /* * SM_NOTIFY: private callback from statd (not part of official NLM proto) */ -static int +static __be32 nlm4svc_proc_sm_notify(struct svc_rqst *rqstp, struct nlm_reboot *argp, void *resp) { - struct sockaddr_in saddr = rqstp->rq_addr; - int vers = argp->vers; - int prot = argp->proto >> 1; - - struct nlm_host *host; - dprintk("lockd: SM_NOTIFY called\n"); - if (saddr.sin_addr.s_addr != htonl(INADDR_LOOPBACK) - || ntohs(saddr.sin_port) >= 1024) { - printk(KERN_WARNING - "lockd: rejected NSM callback from %08x:%d\n", - ntohl(rqstp->rq_addr.sin_addr.s_addr), - ntohs(rqstp->rq_addr.sin_port)); + + if (!nlm_privileged_requester(rqstp)) { + char buf[RPC_MAX_ADDRBUFLEN]; + printk(KERN_WARNING "lockd: rejected NSM callback from %s\n", + svc_print_addr(rqstp, buf, sizeof(buf))); return rpc_system_err; } - /* Obtain the host pointer for this NFS server and try to - * reclaim all locks we hold on this server. - */ - saddr.sin_addr.s_addr = argp->addr; - - if ((argp->proto & 1)==0) { - if ((host = nlmclnt_lookup_host(&saddr, prot, vers)) != NULL) { - nlmclnt_recovery(host, argp->state); - nlm_release_host(host); - } - } else { - /* If we run on an NFS server, delete all locks held by the client */ - - if ((host = nlm_lookup_host(1, &saddr, prot, vers)) != NULL) { - nlmsvc_free_host_resources(host); - nlm_release_host(host); - } - } + nlm_host_rebooted(argp); return rpc_success; } /* * client sent a GRANTED_RES, let's remove the associated block */ -static int +static __be32 nlm4svc_proc_granted_res(struct svc_rqst *rqstp, struct nlm_res *argp, void *resp) { @@ -466,61 +437,12 @@ nlm4svc_proc_granted_res(struct svc_rqst *rqstp, struct nlm_res *argp, dprintk("lockd: GRANTED_RES called\n"); - nlmsvc_grant_reply(rqstp, &argp->cookie, argp->status); + nlmsvc_grant_reply(&argp->cookie, argp->status); return rpc_success; } /* - * This is the generic lockd callback for async RPC calls - */ -static u32 -nlm4svc_callback(struct svc_rqst *rqstp, u32 proc, struct nlm_res *resp) -{ - struct nlm_host *host; - struct nlm_rqst *call; - - if (!(call = nlmclnt_alloc_call())) - return rpc_system_err; - - host = nlmclnt_lookup_host(&rqstp->rq_addr, - rqstp->rq_prot, rqstp->rq_vers); - if (!host) { - kfree(call); - return rpc_system_err; - } - - call->a_flags = RPC_TASK_ASYNC; - call->a_host = host; - memcpy(&call->a_args, resp, sizeof(*resp)); - - if (nlmsvc_async_call(call, proc, &nlm4svc_callback_ops) < 0) - goto error; - - return rpc_success; - error: - kfree(call); - nlm_release_host(host); - return rpc_system_err; -} - -static void nlm4svc_callback_exit(struct rpc_task *task, void *data) -{ - struct nlm_rqst *call = data; - - if (task->tk_status < 0) { - dprintk("lockd: %4d callback failed (errno = %d)\n", - task->tk_pid, -task->tk_status); - } - nlm_release_host(call->a_host); - kfree(call); -} - -static const struct rpc_call_ops nlm4svc_callback_ops = { - .rpc_call_done = nlm4svc_callback_exit, -}; - -/* * NLM Server procedures. */ diff --git a/fs/lockd/svclock.c b/fs/lockd/svclock.c index 9cfced65d4a..ab798a88ec1 100644 --- a/fs/lockd/svclock.c +++ b/fs/lockd/svclock.c @@ -20,16 +20,16 @@ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> */ -#include <linux/config.h> #include <linux/types.h> +#include <linux/slab.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/sched.h> -#include <linux/smp_lock.h> #include <linux/sunrpc/clnt.h> -#include <linux/sunrpc/svc.h> +#include <linux/sunrpc/svc_xprt.h> #include <linux/lockd/nlm.h> #include <linux/lockd/lockd.h> +#include <linux/kthread.h> #define NLMDBG_FACILITY NLMDBG_SVCLOCK @@ -39,90 +39,126 @@ #define nlm_deadlock nlm_lck_denied #endif +static void nlmsvc_release_block(struct nlm_block *block); static void nlmsvc_insert_block(struct nlm_block *block, unsigned long); -static int nlmsvc_remove_block(struct nlm_block *block); +static void nlmsvc_remove_block(struct nlm_block *block); +static int nlmsvc_setgrantargs(struct nlm_rqst *call, struct nlm_lock *lock); +static void nlmsvc_freegrantargs(struct nlm_rqst *call); static const struct rpc_call_ops nlmsvc_grant_ops; /* * The list of blocked locks to retry */ -static struct nlm_block * nlm_blocked; +static LIST_HEAD(nlm_blocked); +static DEFINE_SPINLOCK(nlm_blocked_lock); + +#ifdef LOCKD_DEBUG +static const char *nlmdbg_cookie2a(const struct nlm_cookie *cookie) +{ + /* + * We can get away with a static buffer because we're only + * called with BKL held. + */ + static char buf[2*NLM_MAXCOOKIELEN+1]; + unsigned int i, len = sizeof(buf); + char *p = buf; + + len--; /* allow for trailing \0 */ + if (len < 3) + return "???"; + for (i = 0 ; i < cookie->len ; i++) { + if (len < 2) { + strcpy(p-3, "..."); + break; + } + sprintf(p, "%02x", cookie->data[i]); + p += 2; + len -= 2; + } + *p = '\0'; + + return buf; +} +#endif /* * Insert a blocked lock into the global list */ static void -nlmsvc_insert_block(struct nlm_block *block, unsigned long when) +nlmsvc_insert_block_locked(struct nlm_block *block, unsigned long when) { - struct nlm_block **bp, *b; + struct nlm_block *b; + struct list_head *pos; dprintk("lockd: nlmsvc_insert_block(%p, %ld)\n", block, when); - if (block->b_queued) - nlmsvc_remove_block(block); - bp = &nlm_blocked; + if (list_empty(&block->b_list)) { + kref_get(&block->b_count); + } else { + list_del_init(&block->b_list); + } + + pos = &nlm_blocked; if (when != NLM_NEVER) { if ((when += jiffies) == NLM_NEVER) when ++; - while ((b = *bp) && time_before_eq(b->b_when,when) && b->b_when != NLM_NEVER) - bp = &b->b_next; - } else - while ((b = *bp) != 0) - bp = &b->b_next; + list_for_each(pos, &nlm_blocked) { + b = list_entry(pos, struct nlm_block, b_list); + if (time_after(b->b_when,when) || b->b_when == NLM_NEVER) + break; + } + /* On normal exit from the loop, pos == &nlm_blocked, + * so we will be adding to the end of the list - good + */ + } - block->b_queued = 1; + list_add_tail(&block->b_list, pos); block->b_when = when; - block->b_next = b; - *bp = block; +} + +static void nlmsvc_insert_block(struct nlm_block *block, unsigned long when) +{ + spin_lock(&nlm_blocked_lock); + nlmsvc_insert_block_locked(block, when); + spin_unlock(&nlm_blocked_lock); } /* * Remove a block from the global list */ -static int +static inline void nlmsvc_remove_block(struct nlm_block *block) { - struct nlm_block **bp, *b; - - if (!block->b_queued) - return 1; - for (bp = &nlm_blocked; (b = *bp) != 0; bp = &b->b_next) { - if (b == block) { - *bp = block->b_next; - block->b_queued = 0; - return 1; - } + if (!list_empty(&block->b_list)) { + spin_lock(&nlm_blocked_lock); + list_del_init(&block->b_list); + spin_unlock(&nlm_blocked_lock); + nlmsvc_release_block(block); } - - return 0; } /* - * Find a block for a given lock and optionally remove it from - * the list. + * Find a block for a given lock */ static struct nlm_block * -nlmsvc_lookup_block(struct nlm_file *file, struct nlm_lock *lock, int remove) +nlmsvc_lookup_block(struct nlm_file *file, struct nlm_lock *lock) { - struct nlm_block **head, *block; + struct nlm_block *block; struct file_lock *fl; dprintk("lockd: nlmsvc_lookup_block f=%p pd=%d %Ld-%Ld ty=%d\n", file, lock->fl.fl_pid, (long long)lock->fl.fl_start, (long long)lock->fl.fl_end, lock->fl.fl_type); - for (head = &nlm_blocked; (block = *head) != 0; head = &block->b_next) { - fl = &block->b_call.a_args.lock.fl; + list_for_each_entry(block, &nlm_blocked, b_list) { + fl = &block->b_call->a_args.lock.fl; dprintk("lockd: check f=%p pd=%d %Ld-%Ld ty=%d cookie=%s\n", block->b_file, fl->fl_pid, (long long)fl->fl_start, (long long)fl->fl_end, fl->fl_type, - nlmdbg_cookie2a(&block->b_call.a_args.cookie)); + nlmdbg_cookie2a(&block->b_call->a_args.cookie)); if (block->b_file == file && nlm_compare_locks(fl, &lock->fl)) { - if (remove) { - *head = block->b_next; - block->b_queued = 0; - } + kref_get(&block->b_count); return block; } } @@ -132,9 +168,9 @@ nlmsvc_lookup_block(struct nlm_file *file, struct nlm_lock *lock, int remove) static inline int nlm_cookie_match(struct nlm_cookie *a, struct nlm_cookie *b) { - if(a->len != b->len) + if (a->len != b->len) return 0; - if(memcmp(a->data,b->data,a->len)) + if (memcmp(a->data, b->data, a->len)) return 0; return 1; } @@ -143,18 +179,20 @@ static inline int nlm_cookie_match(struct nlm_cookie *a, struct nlm_cookie *b) * Find a block with a given NLM cookie. */ static inline struct nlm_block * -nlmsvc_find_block(struct nlm_cookie *cookie, struct sockaddr_in *sin) +nlmsvc_find_block(struct nlm_cookie *cookie) { struct nlm_block *block; - for (block = nlm_blocked; block; block = block->b_next) { - dprintk("cookie: head of blocked queue %p, block %p\n", - nlm_blocked, block); - if (nlm_cookie_match(&block->b_call.a_args.cookie,cookie) - && nlm_cmp_addr(sin, &block->b_host->h_addr)) - break; + list_for_each_entry(block, &nlm_blocked, b_list) { + if (nlm_cookie_match(&block->b_call->a_args.cookie,cookie)) + goto found; } + return NULL; + +found: + dprintk("nlmsvc_find_block(%s): block=%p\n", nlmdbg_cookie2a(cookie), block); + kref_get(&block->b_count); return block; } @@ -167,34 +205,39 @@ nlmsvc_find_block(struct nlm_cookie *cookie, struct sockaddr_in *sin) * request, but (as I found out later) that's because some implementations * do just this. Never mind the standards comittees, they support our * logging industries. + * + * 10 years later: I hope we can safely ignore these old and broken + * clients by now. Let's fix this so we can uniquely identify an incoming + * GRANTED_RES message by cookie, without having to rely on the client's IP + * address. --okir */ -static inline struct nlm_block * -nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_file *file, - struct nlm_lock *lock, struct nlm_cookie *cookie) +static struct nlm_block * +nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_host *host, + struct nlm_file *file, struct nlm_lock *lock, + struct nlm_cookie *cookie) { struct nlm_block *block; - struct nlm_host *host; - struct nlm_rqst *call; + struct nlm_rqst *call = NULL; - /* Create host handle for callback */ - host = nlmclnt_lookup_host(&rqstp->rq_addr, - rqstp->rq_prot, rqstp->rq_vers); - if (host == NULL) + call = nlm_alloc_call(host); + if (call == NULL) return NULL; /* Allocate memory for block, and initialize arguments */ - if (!(block = (struct nlm_block *) kmalloc(sizeof(*block), GFP_KERNEL))) + block = kzalloc(sizeof(*block), GFP_KERNEL); + if (block == NULL) goto failed; - memset(block, 0, sizeof(*block)); - locks_init_lock(&block->b_call.a_args.lock.fl); - locks_init_lock(&block->b_call.a_res.lock.fl); + kref_init(&block->b_count); + INIT_LIST_HEAD(&block->b_list); + INIT_LIST_HEAD(&block->b_flist); - if (!nlmclnt_setgrantargs(&block->b_call, lock)) + if (!nlmsvc_setgrantargs(call, lock)) goto failed_free; /* Set notifier function for VFS, and init args */ - block->b_call.a_args.lock.fl.fl_lmops = &nlmsvc_lock_operations; - block->b_call.a_args.cookie = *cookie; /* see above */ + call->a_args.lock.fl.fl_flags |= FL_SLEEP; + call->a_args.lock.fl.fl_lmops = &nlmsvc_lock_operations; + nlmclnt_next_cookie(&call->a_args.cookie); dprintk("lockd: created block %p...\n", block); @@ -202,206 +245,352 @@ nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_file *file, block->b_daemon = rqstp->rq_server; block->b_host = host; block->b_file = file; + block->b_fl = NULL; + file->f_count++; /* Add to file's list of blocks */ - block->b_fnext = file->f_blocks; - file->f_blocks = block; + list_add(&block->b_flist, &file->f_blocks); /* Set up RPC arguments for callback */ - call = &block->b_call; - call->a_host = host; + block->b_call = call; call->a_flags = RPC_TASK_ASYNC; + call->a_block = block; return block; failed_free: kfree(block); failed: - nlm_release_host(host); + nlmsvc_release_call(call); return NULL; } /* - * Delete a block. If the lock was cancelled or the grant callback - * failed, unlock is set to 1. + * Delete a block. * It is the caller's responsibility to check whether the file * can be closed hereafter. */ -static int -nlmsvc_delete_block(struct nlm_block *block, int unlock) +static int nlmsvc_unlink_block(struct nlm_block *block) { - struct file_lock *fl = &block->b_call.a_args.lock.fl; - struct nlm_file *file = block->b_file; - struct nlm_block **bp; - int status = 0; - - dprintk("lockd: deleting block %p...\n", block); + int status; + dprintk("lockd: unlinking block %p...\n", block); /* Remove block from list */ + status = posix_unblock_lock(&block->b_call->a_args.lock.fl); nlmsvc_remove_block(block); - if (unlock) - status = posix_unblock_lock(file->f_file, fl); + return status; +} - /* If the block is in the middle of a GRANT callback, - * don't kill it yet. */ - if (block->b_incall) { - nlmsvc_insert_block(block, NLM_NEVER); - block->b_done = 1; - return status; - } +static void nlmsvc_free_block(struct kref *kref) +{ + struct nlm_block *block = container_of(kref, struct nlm_block, b_count); + struct nlm_file *file = block->b_file; + + dprintk("lockd: freeing block %p...\n", block); /* Remove block from file's list of blocks */ - for (bp = &file->f_blocks; *bp; bp = &(*bp)->b_fnext) { - if (*bp == block) { - *bp = block->b_fnext; - break; - } - } + list_del_init(&block->b_flist); + mutex_unlock(&file->f_mutex); - if (block->b_host) - nlm_release_host(block->b_host); - nlmclnt_freegrantargs(&block->b_call); + nlmsvc_freegrantargs(block->b_call); + nlmsvc_release_call(block->b_call); + nlm_release_file(block->b_file); + kfree(block->b_fl); kfree(block); - return status; +} + +static void nlmsvc_release_block(struct nlm_block *block) +{ + if (block != NULL) + kref_put_mutex(&block->b_count, nlmsvc_free_block, &block->b_file->f_mutex); } /* - * Loop over all blocks and perform the action specified. - * (NLM_ACT_CHECK handled by nlmsvc_inspect_file). + * Loop over all blocks and delete blocks held by + * a matching host. */ -int -nlmsvc_traverse_blocks(struct nlm_host *host, struct nlm_file *file, int action) +void nlmsvc_traverse_blocks(struct nlm_host *host, + struct nlm_file *file, + nlm_host_match_fn_t match) { - struct nlm_block *block, *next; - /* XXX: Will everything get cleaned up if we don't unlock here? */ - - down(&file->f_sema); - for (block = file->f_blocks; block; block = next) { - next = block->b_fnext; - if (action == NLM_ACT_MARK) - block->b_host->h_inuse = 1; - else if (action == NLM_ACT_UNLOCK) { - if (host == NULL || host == block->b_host) - nlmsvc_delete_block(block, 1); - } + struct nlm_block *block, *next; + +restart: + mutex_lock(&file->f_mutex); + list_for_each_entry_safe(block, next, &file->f_blocks, b_flist) { + if (!match(block->b_host, host)) + continue; + /* Do not destroy blocks that are not on + * the global retry list - why? */ + if (list_empty(&block->b_list)) + continue; + kref_get(&block->b_count); + mutex_unlock(&file->f_mutex); + nlmsvc_unlink_block(block); + nlmsvc_release_block(block); + goto restart; + } + mutex_unlock(&file->f_mutex); +} + +/* + * Initialize arguments for GRANTED call. The nlm_rqst structure + * has been cleared already. + */ +static int nlmsvc_setgrantargs(struct nlm_rqst *call, struct nlm_lock *lock) +{ + locks_copy_lock(&call->a_args.lock.fl, &lock->fl); + memcpy(&call->a_args.lock.fh, &lock->fh, sizeof(call->a_args.lock.fh)); + call->a_args.lock.caller = utsname()->nodename; + call->a_args.lock.oh.len = lock->oh.len; + + /* set default data area */ + call->a_args.lock.oh.data = call->a_owner; + call->a_args.lock.svid = lock->fl.fl_pid; + + if (lock->oh.len > NLMCLNT_OHSIZE) { + void *data = kmalloc(lock->oh.len, GFP_KERNEL); + if (!data) + return 0; + call->a_args.lock.oh.data = (u8 *) data; + } + + memcpy(call->a_args.lock.oh.data, lock->oh.data, lock->oh.len); + return 1; +} + +static void nlmsvc_freegrantargs(struct nlm_rqst *call) +{ + if (call->a_args.lock.oh.data != call->a_owner) + kfree(call->a_args.lock.oh.data); + + locks_release_private(&call->a_args.lock.fl); +} + +/* + * Deferred lock request handling for non-blocking lock + */ +static __be32 +nlmsvc_defer_lock_rqst(struct svc_rqst *rqstp, struct nlm_block *block) +{ + __be32 status = nlm_lck_denied_nolocks; + + block->b_flags |= B_QUEUED; + + nlmsvc_insert_block(block, NLM_TIMEOUT); + + block->b_cache_req = &rqstp->rq_chandle; + if (rqstp->rq_chandle.defer) { + block->b_deferred_req = + rqstp->rq_chandle.defer(block->b_cache_req); + if (block->b_deferred_req != NULL) + status = nlm_drop_reply; } - up(&file->f_sema); - return 0; + dprintk("lockd: nlmsvc_defer_lock_rqst block %p flags %d status %d\n", + block, block->b_flags, ntohl(status)); + + return status; } /* * Attempt to establish a lock, and if it can't be granted, block it * if required. */ -u32 +__be32 nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file, - struct nlm_lock *lock, int wait, struct nlm_cookie *cookie) + struct nlm_host *host, struct nlm_lock *lock, int wait, + struct nlm_cookie *cookie, int reclaim) { - struct file_lock *conflock; - struct nlm_block *block; + struct nlm_block *block = NULL; int error; + __be32 ret; dprintk("lockd: nlmsvc_lock(%s/%ld, ty=%d, pi=%d, %Ld-%Ld, bl=%d)\n", - file->f_file->f_dentry->d_inode->i_sb->s_id, - file->f_file->f_dentry->d_inode->i_ino, + file_inode(file->f_file)->i_sb->s_id, + file_inode(file->f_file)->i_ino, lock->fl.fl_type, lock->fl.fl_pid, (long long)lock->fl.fl_start, (long long)lock->fl.fl_end, wait); - - /* Get existing block (in case client is busy-waiting) */ - block = nlmsvc_lookup_block(file, lock, 0); - - lock->fl.fl_flags |= FL_LOCKD; - -again: /* Lock file against concurrent access */ - down(&file->f_sema); - - if (!(conflock = posix_test_lock(file->f_file, &lock->fl))) { - error = posix_lock_file(file->f_file, &lock->fl); - - if (block) - nlmsvc_delete_block(block, 0); - up(&file->f_sema); - - dprintk("lockd: posix_lock_file returned %d\n", -error); - switch(-error) { - case 0: - return nlm_granted; - case EDEADLK: - return nlm_deadlock; - case EAGAIN: - return nlm_lck_denied; - default: /* includes ENOLCK */ - return nlm_lck_denied_nolocks; + mutex_lock(&file->f_mutex); + /* Get existing block (in case client is busy-waiting) + * or create new block + */ + block = nlmsvc_lookup_block(file, lock); + if (block == NULL) { + block = nlmsvc_create_block(rqstp, host, file, lock, cookie); + ret = nlm_lck_denied_nolocks; + if (block == NULL) + goto out; + lock = &block->b_call->a_args.lock; + } else + lock->fl.fl_flags &= ~FL_SLEEP; + + if (block->b_flags & B_QUEUED) { + dprintk("lockd: nlmsvc_lock deferred block %p flags %d\n", + block, block->b_flags); + if (block->b_granted) { + nlmsvc_unlink_block(block); + ret = nlm_granted; + goto out; } + if (block->b_flags & B_TIMED_OUT) { + nlmsvc_unlink_block(block); + ret = nlm_lck_denied; + goto out; + } + ret = nlm_drop_reply; + goto out; } - if (!wait) { - up(&file->f_sema); - return nlm_lck_denied; + if (locks_in_grace(SVC_NET(rqstp)) && !reclaim) { + ret = nlm_lck_denied_grace_period; + goto out; } - - if (posix_locks_deadlock(&lock->fl, conflock)) { - up(&file->f_sema); - return nlm_deadlock; + if (reclaim && !locks_in_grace(SVC_NET(rqstp))) { + ret = nlm_lck_denied_grace_period; + goto out; } - /* If we don't have a block, create and initialize it. Then - * retry because we may have slept in kmalloc. */ - /* We have to release f_sema as nlmsvc_create_block may try to - * to claim it while doing host garbage collection */ - if (block == NULL) { - up(&file->f_sema); - dprintk("lockd: blocking on this lock (allocating).\n"); - if (!(block = nlmsvc_create_block(rqstp, file, lock, cookie))) - return nlm_lck_denied_nolocks; - goto again; + if (!wait) + lock->fl.fl_flags &= ~FL_SLEEP; + error = vfs_lock_file(file->f_file, F_SETLK, &lock->fl, NULL); + lock->fl.fl_flags &= ~FL_SLEEP; + + dprintk("lockd: vfs_lock_file returned %d\n", error); + switch (error) { + case 0: + ret = nlm_granted; + goto out; + case -EAGAIN: + /* + * If this is a blocking request for an + * already pending lock request then we need + * to put it back on lockd's block list + */ + if (wait) + break; + ret = nlm_lck_denied; + goto out; + case FILE_LOCK_DEFERRED: + if (wait) + break; + /* Filesystem lock operation is in progress + Add it to the queue waiting for callback */ + ret = nlmsvc_defer_lock_rqst(rqstp, block); + goto out; + case -EDEADLK: + ret = nlm_deadlock; + goto out; + default: /* includes ENOLCK */ + ret = nlm_lck_denied_nolocks; + goto out; } + ret = nlm_lck_blocked; + /* Append to list of blocked */ nlmsvc_insert_block(block, NLM_NEVER); - - if (list_empty(&block->b_call.a_args.lock.fl.fl_block)) { - /* Now add block to block list of the conflicting lock - if we haven't done so. */ - dprintk("lockd: blocking on this lock.\n"); - posix_block_lock(conflock, &block->b_call.a_args.lock.fl); - } - - up(&file->f_sema); - return nlm_lck_blocked; +out: + mutex_unlock(&file->f_mutex); + nlmsvc_release_block(block); + dprintk("lockd: nlmsvc_lock returned %u\n", ret); + return ret; } /* * Test for presence of a conflicting lock. */ -u32 -nlmsvc_testlock(struct nlm_file *file, struct nlm_lock *lock, - struct nlm_lock *conflock) +__be32 +nlmsvc_testlock(struct svc_rqst *rqstp, struct nlm_file *file, + struct nlm_host *host, struct nlm_lock *lock, + struct nlm_lock *conflock, struct nlm_cookie *cookie) { - struct file_lock *fl; + struct nlm_block *block = NULL; + int error; + __be32 ret; dprintk("lockd: nlmsvc_testlock(%s/%ld, ty=%d, %Ld-%Ld)\n", - file->f_file->f_dentry->d_inode->i_sb->s_id, - file->f_file->f_dentry->d_inode->i_ino, + file_inode(file->f_file)->i_sb->s_id, + file_inode(file->f_file)->i_ino, lock->fl.fl_type, (long long)lock->fl.fl_start, (long long)lock->fl.fl_end); - if ((fl = posix_test_lock(file->f_file, &lock->fl)) != NULL) { - dprintk("lockd: conflicting lock(ty=%d, %Ld-%Ld)\n", - fl->fl_type, (long long)fl->fl_start, - (long long)fl->fl_end); - conflock->caller = "somehost"; /* FIXME */ - conflock->oh.len = 0; /* don't return OH info */ - conflock->fl = *fl; - return nlm_lck_denied; + /* Get existing block (in case client is busy-waiting) */ + block = nlmsvc_lookup_block(file, lock); + + if (block == NULL) { + struct file_lock *conf = kzalloc(sizeof(*conf), GFP_KERNEL); + + if (conf == NULL) + return nlm_granted; + block = nlmsvc_create_block(rqstp, host, file, lock, cookie); + if (block == NULL) { + kfree(conf); + return nlm_granted; + } + block->b_fl = conf; + } + if (block->b_flags & B_QUEUED) { + dprintk("lockd: nlmsvc_testlock deferred block %p flags %d fl %p\n", + block, block->b_flags, block->b_fl); + if (block->b_flags & B_TIMED_OUT) { + nlmsvc_unlink_block(block); + ret = nlm_lck_denied; + goto out; + } + if (block->b_flags & B_GOT_CALLBACK) { + nlmsvc_unlink_block(block); + if (block->b_fl != NULL + && block->b_fl->fl_type != F_UNLCK) { + lock->fl = *block->b_fl; + goto conf_lock; + } else { + ret = nlm_granted; + goto out; + } + } + ret = nlm_drop_reply; + goto out; + } + + if (locks_in_grace(SVC_NET(rqstp))) { + ret = nlm_lck_denied_grace_period; + goto out; + } + error = vfs_test_lock(file->f_file, &lock->fl); + if (error == FILE_LOCK_DEFERRED) { + ret = nlmsvc_defer_lock_rqst(rqstp, block); + goto out; + } + if (error) { + ret = nlm_lck_denied_nolocks; + goto out; + } + if (lock->fl.fl_type == F_UNLCK) { + ret = nlm_granted; + goto out; } - return nlm_granted; +conf_lock: + dprintk("lockd: conflicting lock(ty=%d, %Ld-%Ld)\n", + lock->fl.fl_type, (long long)lock->fl.fl_start, + (long long)lock->fl.fl_end); + conflock->caller = "somehost"; /* FIXME */ + conflock->len = strlen(conflock->caller); + conflock->oh.len = 0; /* don't return OH info */ + conflock->svid = lock->fl.fl_pid; + conflock->fl.fl_type = lock->fl.fl_type; + conflock->fl.fl_start = lock->fl.fl_start; + conflock->fl.fl_end = lock->fl.fl_end; + ret = nlm_lck_denied; +out: + if (block) + nlmsvc_release_block(block); + return ret; } /* @@ -411,23 +600,23 @@ nlmsvc_testlock(struct nlm_file *file, struct nlm_lock *lock, * afterwards. In this case the block will still be there, and hence * must be removed. */ -u32 -nlmsvc_unlock(struct nlm_file *file, struct nlm_lock *lock) +__be32 +nlmsvc_unlock(struct net *net, struct nlm_file *file, struct nlm_lock *lock) { int error; dprintk("lockd: nlmsvc_unlock(%s/%ld, pi=%d, %Ld-%Ld)\n", - file->f_file->f_dentry->d_inode->i_sb->s_id, - file->f_file->f_dentry->d_inode->i_ino, + file_inode(file->f_file)->i_sb->s_id, + file_inode(file->f_file)->i_ino, lock->fl.fl_pid, (long long)lock->fl.fl_start, (long long)lock->fl.fl_end); /* First, cancel any lock that might be there */ - nlmsvc_cancel_blocked(file, lock); + nlmsvc_cancel_blocked(net, file, lock); lock->fl.fl_type = F_UNLCK; - error = posix_lock_file(file->f_file, &lock->fl); + error = vfs_lock_file(file->f_file, F_SETLK, &lock->fl, NULL); return (error < 0)? nlm_lck_denied_nolocks : nlm_granted; } @@ -439,27 +628,92 @@ nlmsvc_unlock(struct nlm_file *file, struct nlm_lock *lock) * be in progress. * The calling procedure must check whether the file can be closed. */ -u32 -nlmsvc_cancel_blocked(struct nlm_file *file, struct nlm_lock *lock) +__be32 +nlmsvc_cancel_blocked(struct net *net, struct nlm_file *file, struct nlm_lock *lock) { struct nlm_block *block; int status = 0; dprintk("lockd: nlmsvc_cancel(%s/%ld, pi=%d, %Ld-%Ld)\n", - file->f_file->f_dentry->d_inode->i_sb->s_id, - file->f_file->f_dentry->d_inode->i_ino, + file_inode(file->f_file)->i_sb->s_id, + file_inode(file->f_file)->i_ino, lock->fl.fl_pid, (long long)lock->fl.fl_start, (long long)lock->fl.fl_end); - down(&file->f_sema); - if ((block = nlmsvc_lookup_block(file, lock, 1)) != NULL) - status = nlmsvc_delete_block(block, 1); - up(&file->f_sema); + if (locks_in_grace(net)) + return nlm_lck_denied_grace_period; + + mutex_lock(&file->f_mutex); + block = nlmsvc_lookup_block(file, lock); + mutex_unlock(&file->f_mutex); + if (block != NULL) { + vfs_cancel_lock(block->b_file->f_file, + &block->b_call->a_args.lock.fl); + status = nlmsvc_unlink_block(block); + nlmsvc_release_block(block); + } return status ? nlm_lck_denied : nlm_granted; } /* + * This is a callback from the filesystem for VFS file lock requests. + * It will be used if lm_grant is defined and the filesystem can not + * respond to the request immediately. + * For GETLK request it will copy the reply to the nlm_block. + * For SETLK or SETLKW request it will get the local posix lock. + * In all cases it will move the block to the head of nlm_blocked q where + * nlmsvc_retry_blocked() can send back a reply for SETLKW or revisit the + * deferred rpc for GETLK and SETLK. + */ +static void +nlmsvc_update_deferred_block(struct nlm_block *block, struct file_lock *conf, + int result) +{ + block->b_flags |= B_GOT_CALLBACK; + if (result == 0) + block->b_granted = 1; + else + block->b_flags |= B_TIMED_OUT; + if (conf) { + if (block->b_fl) + __locks_copy_lock(block->b_fl, conf); + } +} + +static int nlmsvc_grant_deferred(struct file_lock *fl, struct file_lock *conf, + int result) +{ + struct nlm_block *block; + int rc = -ENOENT; + + spin_lock(&nlm_blocked_lock); + list_for_each_entry(block, &nlm_blocked, b_list) { + if (nlm_compare_locks(&block->b_call->a_args.lock.fl, fl)) { + dprintk("lockd: nlmsvc_notify_blocked block %p flags %d\n", + block, block->b_flags); + if (block->b_flags & B_QUEUED) { + if (block->b_flags & B_TIMED_OUT) { + rc = -ENOLCK; + break; + } + nlmsvc_update_deferred_block(block, conf, result); + } else if (result == 0) + block->b_granted = 1; + + nlmsvc_insert_block_locked(block, 0); + svc_wake_up(block->b_daemon); + rc = 0; + break; + } + } + spin_unlock(&nlm_blocked_lock); + if (rc == -ENOENT) + printk(KERN_WARNING "lockd: grant for unknown block\n"); + return rc; +} + +/* * Unblock a blocked lock request. This is a callback invoked from the * VFS layer when a lock on which we blocked is removed. * @@ -469,17 +723,19 @@ nlmsvc_cancel_blocked(struct nlm_file *file, struct nlm_lock *lock) static void nlmsvc_notify_blocked(struct file_lock *fl) { - struct nlm_block **bp, *block; + struct nlm_block *block; dprintk("lockd: VFS unblock notification for block %p\n", fl); - for (bp = &nlm_blocked; (block = *bp) != 0; bp = &block->b_next) { - if (nlm_compare_locks(&block->b_call.a_args.lock.fl, fl)) { - nlmsvc_insert_block(block, 0); + spin_lock(&nlm_blocked_lock); + list_for_each_entry(block, &nlm_blocked, b_list) { + if (nlm_compare_locks(&block->b_call->a_args.lock.fl, fl)) { + nlmsvc_insert_block_locked(block, 0); + spin_unlock(&nlm_blocked_lock); svc_wake_up(block->b_daemon); return; } } - + spin_unlock(&nlm_blocked_lock); printk(KERN_WARNING "lockd: notification for unknown block!\n"); } @@ -488,9 +744,22 @@ static int nlmsvc_same_owner(struct file_lock *fl1, struct file_lock *fl2) return fl1->fl_owner == fl2->fl_owner && fl1->fl_pid == fl2->fl_pid; } -struct lock_manager_operations nlmsvc_lock_operations = { - .fl_compare_owner = nlmsvc_same_owner, - .fl_notify = nlmsvc_notify_blocked, +/* + * Since NLM uses two "keys" for tracking locks, we need to hash them down + * to one for the blocked_hash. Here, we're just xor'ing the host address + * with the pid in order to create a key value for picking a hash bucket. + */ +static unsigned long +nlmsvc_owner_key(struct file_lock *fl) +{ + return (unsigned long)fl->fl_owner ^ (unsigned long)fl->fl_pid; +} + +const struct lock_manager_operations nlmsvc_lock_operations = { + .lm_compare_owner = nlmsvc_same_owner, + .lm_owner_key = nlmsvc_owner_key, + .lm_notify = nlmsvc_notify_blocked, + .lm_grant = nlmsvc_grant_deferred, }; /* @@ -508,17 +777,16 @@ static void nlmsvc_grant_blocked(struct nlm_block *block) { struct nlm_file *file = block->b_file; - struct nlm_lock *lock = &block->b_call.a_args.lock; - struct file_lock *conflock; + struct nlm_lock *lock = &block->b_call->a_args.lock; int error; + loff_t fl_start, fl_end; dprintk("lockd: grant blocked lock %p\n", block); - /* First thing is lock the file */ - down(&file->f_sema); + kref_get(&block->b_count); /* Unlink block request from list */ - nlmsvc_remove_block(block); + nlmsvc_unlink_block(block); /* If b_granted is true this means we've been here before. * Just retry the grant callback, possibly refreshing the RPC @@ -529,24 +797,30 @@ nlmsvc_grant_blocked(struct nlm_block *block) } /* Try the lock operation again */ - if ((conflock = posix_test_lock(file->f_file, &lock->fl)) != NULL) { - /* Bummer, we blocked again */ - dprintk("lockd: lock still blocked\n"); + /* vfs_lock_file() can mangle fl_start and fl_end, but we need + * them unchanged for the GRANT_MSG + */ + lock->fl.fl_flags |= FL_SLEEP; + fl_start = lock->fl.fl_start; + fl_end = lock->fl.fl_end; + error = vfs_lock_file(file->f_file, F_SETLK, &lock->fl, NULL); + lock->fl.fl_flags &= ~FL_SLEEP; + lock->fl.fl_start = fl_start; + lock->fl.fl_end = fl_end; + + switch (error) { + case 0: + break; + case FILE_LOCK_DEFERRED: + dprintk("lockd: lock still blocked error %d\n", error); nlmsvc_insert_block(block, NLM_NEVER); - posix_block_lock(conflock, &lock->fl); - up(&file->f_sema); + nlmsvc_release_block(block); return; - } - - /* Alright, no conflicting lock. Now lock it for real. If the - * following yields an error, this is most probably due to low - * memory. Retry the lock in a few seconds. - */ - if ((error = posix_lock_file(file->f_file, &lock->fl)) < 0) { + default: printk(KERN_WARNING "lockd: unexpected error %d in %s!\n", - -error, __FUNCTION__); + -error, __func__); nlmsvc_insert_block(block, 10 * HZ); - up(&file->f_sema); + nlmsvc_release_block(block); return; } @@ -554,17 +828,21 @@ callback: /* Lock was granted by VFS. */ dprintk("lockd: GRANTing blocked lock.\n"); block->b_granted = 1; - block->b_incall = 1; - /* Schedule next grant callback in 30 seconds */ - nlmsvc_insert_block(block, 30 * HZ); + /* keep block on the list, but don't reattempt until the RPC + * completes or the submission fails + */ + nlmsvc_insert_block(block, NLM_NEVER); + + /* Call the client -- use a soft RPC task since nlmsvc_retry_blocked + * will queue up a new one if this one times out + */ + error = nlm_async_call(block->b_call, NLMPROC_GRANTED_MSG, + &nlmsvc_grant_ops); - /* Call the client */ - nlm_get_host(block->b_call.a_host); - if (nlmsvc_async_call(&block->b_call, NLMPROC_GRANTED_MSG, - &nlmsvc_grant_ops) < 0) - nlm_release_host(block->b_call.a_host); - up(&file->f_sema); + /* RPC submission failed, wait a bit and retry */ + if (error < 0) + nlmsvc_insert_block(block, 10 * HZ); } /* @@ -578,20 +856,22 @@ callback: static void nlmsvc_grant_callback(struct rpc_task *task, void *data) { struct nlm_rqst *call = data; - struct nlm_block *block; + struct nlm_block *block = call->a_block; unsigned long timeout; - struct sockaddr_in *peer_addr = RPC_PEERADDR(task->tk_client); dprintk("lockd: GRANT_MSG RPC callback\n"); - dprintk("callback: looking for cookie %s, host (%u.%u.%u.%u)\n", - nlmdbg_cookie2a(&call->a_args.cookie), - NIPQUAD(peer_addr->sin_addr.s_addr)); - if (!(block = nlmsvc_find_block(&call->a_args.cookie, peer_addr))) { - dprintk("lockd: no block for cookie %s, host (%u.%u.%u.%u)\n", - nlmdbg_cookie2a(&call->a_args.cookie), - NIPQUAD(peer_addr->sin_addr.s_addr)); - return; - } + + spin_lock(&nlm_blocked_lock); + /* if the block is not on a list at this point then it has + * been invalidated. Don't try to requeue it. + * + * FIXME: it's possible that the block is removed from the list + * after this check but before the nlmsvc_insert_block. In that + * case it will be added back. Perhaps we need better locking + * for nlm_blocked? + */ + if (list_empty(&block->b_list)) + goto out; /* Technically, we should down the file semaphore here. Since we * move the block towards the head of the queue only, no harm @@ -599,22 +879,29 @@ static void nlmsvc_grant_callback(struct rpc_task *task, void *data) if (task->tk_status < 0) { /* RPC error: Re-insert for retransmission */ timeout = 10 * HZ; - } else if (block->b_done) { - /* Block already removed, kill it for real */ - timeout = 0; } else { /* Call was successful, now wait for client callback */ timeout = 60 * HZ; } - nlmsvc_insert_block(block, timeout); + nlmsvc_insert_block_locked(block, timeout); svc_wake_up(block->b_daemon); - block->b_incall = 0; +out: + spin_unlock(&nlm_blocked_lock); +} - nlm_release_host(call->a_host); +/* + * FIXME: nlmsvc_release_block() grabs a mutex. This is not allowed for an + * .rpc_release rpc_call_op + */ +static void nlmsvc_grant_release(void *data) +{ + struct nlm_rqst *call = data; + nlmsvc_release_block(call->a_block); } static const struct rpc_call_ops nlmsvc_grant_ops = { .rpc_call_done = nlmsvc_grant_callback, + .rpc_release = nlmsvc_grant_release, }; /* @@ -622,37 +909,43 @@ static const struct rpc_call_ops nlmsvc_grant_ops = { * block. */ void -nlmsvc_grant_reply(struct svc_rqst *rqstp, struct nlm_cookie *cookie, u32 status) +nlmsvc_grant_reply(struct nlm_cookie *cookie, __be32 status) { struct nlm_block *block; - struct nlm_file *file; - dprintk("grant_reply: looking for cookie %x, host (%08x), s=%d \n", - *(unsigned int *)(cookie->data), - ntohl(rqstp->rq_addr.sin_addr.s_addr), status); - if (!(block = nlmsvc_find_block(cookie, &rqstp->rq_addr))) + dprintk("grant_reply: looking for cookie %x, s=%d \n", + *(unsigned int *)(cookie->data), status); + if (!(block = nlmsvc_find_block(cookie))) return; - file = block->b_file; - file->f_count++; - down(&file->f_sema); - block = nlmsvc_find_block(cookie, &rqstp->rq_addr); if (block) { - if (status == NLM_LCK_DENIED_GRACE_PERIOD) { + if (status == nlm_lck_denied_grace_period) { /* Try again in a couple of seconds */ nlmsvc_insert_block(block, 10 * HZ); - up(&file->f_sema); } else { /* Lock is now held by client, or has been rejected. * In both cases, the block should be removed. */ - up(&file->f_sema); - if (status == NLM_LCK_GRANTED) - nlmsvc_delete_block(block, 0); - else - nlmsvc_delete_block(block, 1); + nlmsvc_unlink_block(block); } } - nlm_release_file(file); + nlmsvc_release_block(block); +} + +/* Helper function to handle retry of a deferred block. + * If it is a blocking lock, call grant_blocked. + * For a non-blocking lock or test lock, revisit the request. + */ +static void +retry_deferred_block(struct nlm_block *block) +{ + if (!(block->b_flags & B_GOT_CALLBACK)) + block->b_flags |= B_TIMED_OUT; + nlmsvc_insert_block(block, NLM_TIMEOUT); + dprintk("revisit block %p flags %d\n", block, block->b_flags); + if (block->b_deferred_req) { + block->b_deferred_req->revisit(block->b_deferred_req, 0); + block->b_deferred_req = NULL; + } } /* @@ -663,26 +956,32 @@ nlmsvc_grant_reply(struct svc_rqst *rqstp, struct nlm_cookie *cookie, u32 status unsigned long nlmsvc_retry_blocked(void) { - struct nlm_block *block; + unsigned long timeout = MAX_SCHEDULE_TIMEOUT; + struct nlm_block *block; + + spin_lock(&nlm_blocked_lock); + while (!list_empty(&nlm_blocked) && !kthread_should_stop()) { + block = list_entry(nlm_blocked.next, struct nlm_block, b_list); - dprintk("nlmsvc_retry_blocked(%p, when=%ld)\n", - nlm_blocked, - nlm_blocked? nlm_blocked->b_when : 0); - while ((block = nlm_blocked) != 0) { if (block->b_when == NLM_NEVER) break; - if (time_after(block->b_when,jiffies)) + if (time_after(block->b_when, jiffies)) { + timeout = block->b_when - jiffies; break; - dprintk("nlmsvc_retry_blocked(%p, when=%ld, done=%d)\n", - block, block->b_when, block->b_done); - if (block->b_done) - nlmsvc_delete_block(block, 0); - else + } + spin_unlock(&nlm_blocked_lock); + + dprintk("nlmsvc_retry_blocked(%p, when=%ld)\n", + block, block->b_when); + if (block->b_flags & B_QUEUED) { + dprintk("nlmsvc_retry_blocked delete block (%p, granted=%d, flags=%d)\n", + block, block->b_granted, block->b_flags); + retry_deferred_block(block); + } else nlmsvc_grant_blocked(block); + spin_lock(&nlm_blocked_lock); } + spin_unlock(&nlm_blocked_lock); - if ((block = nlm_blocked) && block->b_when != NLM_NEVER) - return (block->b_when - jiffies); - - return MAX_SCHEDULE_TIMEOUT; + return timeout; } diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c index 3bc437e0cf5..21171f0c647 100644 --- a/fs/lockd/svcproc.c +++ b/fs/lockd/svcproc.c @@ -7,28 +7,17 @@ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> */ -#include <linux/config.h> #include <linux/types.h> #include <linux/time.h> -#include <linux/slab.h> -#include <linux/in.h> -#include <linux/sunrpc/svc.h> -#include <linux/sunrpc/clnt.h> -#include <linux/nfsd/nfsd.h> #include <linux/lockd/lockd.h> #include <linux/lockd/share.h> -#include <linux/lockd/sm_inter.h> - +#include <linux/sunrpc/svc_xprt.h> #define NLMDBG_FACILITY NLMDBG_CLIENT -static u32 nlmsvc_callback(struct svc_rqst *, u32, struct nlm_res *); - -static const struct rpc_call_ops nlmsvc_callback_ops; - #ifdef CONFIG_LOCKD_V4 -static u32 -cast_to_nlm(u32 status, u32 vers) +static __be32 +cast_to_nlm(__be32 status, u32 vers) { /* Note: status is assumed to be in network byte order !!! */ if (vers != 4){ @@ -38,6 +27,7 @@ cast_to_nlm(u32 status, u32 vers) case nlm_lck_denied_nolocks: case nlm_lck_blocked: case nlm_lck_denied_grace_period: + case nlm_drop_reply: break; case nlm4_deadlock: status = nlm_lck_denied; @@ -57,28 +47,29 @@ cast_to_nlm(u32 status, u32 vers) /* * Obtain client and file from arguments */ -static u32 +static __be32 nlmsvc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_host **hostp, struct nlm_file **filp) { struct nlm_host *host = NULL; struct nlm_file *file = NULL; struct nlm_lock *lock = &argp->lock; - u32 error; + __be32 error = 0; /* nfsd callbacks must have been installed for this procedure */ if (!nlmsvc_ops) return nlm_lck_denied_nolocks; /* Obtain host handle */ - if (!(host = nlmsvc_lookup_host(rqstp)) - || (argp->monitor && !host->h_monitored && nsm_monitor(host) < 0)) + if (!(host = nlmsvc_lookup_host(rqstp, lock->caller, lock->len)) + || (argp->monitor && nsm_monitor(host) < 0)) goto no_locks; *hostp = host; /* Obtain file pointer. Not used by FREE_ALL call. */ if (filp != NULL) { - if ((error = nlm_lookup_file(rqstp, &file, &lock->fh)) != 0) + error = cast_status(nlm_lookup_file(rqstp, &file, &lock->fh)); + if (error != 0) goto no_locks; *filp = file; @@ -91,15 +82,16 @@ nlmsvc_retrieve_args(struct svc_rqst *rqstp, struct nlm_args *argp, return 0; no_locks: - if (host) - nlm_release_host(host); + nlmsvc_release_host(host); + if (error) + return error; return nlm_lck_denied_nolocks; } /* * NULL: Test for presence of service */ -static int +static __be32 nlmsvc_proc_null(struct svc_rqst *rqstp, void *argp, void *resp) { dprintk("lockd: NULL called\n"); @@ -109,56 +101,49 @@ nlmsvc_proc_null(struct svc_rqst *rqstp, void *argp, void *resp) /* * TEST: Check for conflicting lock */ -static int +static __be32 nlmsvc_proc_test(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { struct nlm_host *host; struct nlm_file *file; + __be32 rc = rpc_success; dprintk("lockd: TEST called\n"); resp->cookie = argp->cookie; - /* Don't accept test requests during grace period */ - if (nlmsvc_grace_period) { - resp->status = nlm_lck_denied_grace_period; - return rpc_success; - } - /* Obtain client and file */ if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; /* Now check for conflicting locks */ - resp->status = cast_status(nlmsvc_testlock(file, &argp->lock, &resp->lock)); - - dprintk("lockd: TEST status %d vers %d\n", - ntohl(resp->status), rqstp->rq_vers); - nlm_release_host(host); + resp->status = cast_status(nlmsvc_testlock(rqstp, file, host, &argp->lock, &resp->lock, &resp->cookie)); + if (resp->status == nlm_drop_reply) + rc = rpc_drop_reply; + else + dprintk("lockd: TEST status %d vers %d\n", + ntohl(resp->status), rqstp->rq_vers); + + nlmsvc_release_host(host); nlm_release_file(file); - return rpc_success; + return rc; } -static int +static __be32 nlmsvc_proc_lock(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { struct nlm_host *host; struct nlm_file *file; + __be32 rc = rpc_success; dprintk("lockd: LOCK called\n"); resp->cookie = argp->cookie; - /* Don't accept new lock requests during grace period */ - if (nlmsvc_grace_period && !argp->reclaim) { - resp->status = nlm_lck_denied_grace_period; - return rpc_success; - } - /* Obtain client and file */ if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; #if 0 /* If supplied state doesn't match current state, we assume it's @@ -173,41 +158,46 @@ nlmsvc_proc_lock(struct svc_rqst *rqstp, struct nlm_args *argp, #endif /* Now try to lock the file */ - resp->status = cast_status(nlmsvc_lock(rqstp, file, &argp->lock, - argp->block, &argp->cookie)); - - dprintk("lockd: LOCK status %d\n", ntohl(resp->status)); - nlm_release_host(host); + resp->status = cast_status(nlmsvc_lock(rqstp, file, host, &argp->lock, + argp->block, &argp->cookie, + argp->reclaim)); + if (resp->status == nlm_drop_reply) + rc = rpc_drop_reply; + else + dprintk("lockd: LOCK status %d\n", ntohl(resp->status)); + + nlmsvc_release_host(host); nlm_release_file(file); - return rpc_success; + return rc; } -static int +static __be32 nlmsvc_proc_cancel(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { struct nlm_host *host; struct nlm_file *file; + struct net *net = SVC_NET(rqstp); dprintk("lockd: CANCEL called\n"); resp->cookie = argp->cookie; /* Don't accept requests during grace period */ - if (nlmsvc_grace_period) { + if (locks_in_grace(net)) { resp->status = nlm_lck_denied_grace_period; return rpc_success; } /* Obtain client and file */ if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; /* Try to cancel request. */ - resp->status = cast_status(nlmsvc_cancel_blocked(file, &argp->lock)); + resp->status = cast_status(nlmsvc_cancel_blocked(net, file, &argp->lock)); dprintk("lockd: CANCEL status %d\n", ntohl(resp->status)); - nlm_release_host(host); + nlmsvc_release_host(host); nlm_release_file(file); return rpc_success; } @@ -215,32 +205,33 @@ nlmsvc_proc_cancel(struct svc_rqst *rqstp, struct nlm_args *argp, /* * UNLOCK: release a lock */ -static int +static __be32 nlmsvc_proc_unlock(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { struct nlm_host *host; struct nlm_file *file; + struct net *net = SVC_NET(rqstp); dprintk("lockd: UNLOCK called\n"); resp->cookie = argp->cookie; /* Don't accept new lock requests during grace period */ - if (nlmsvc_grace_period) { + if (locks_in_grace(net)) { resp->status = nlm_lck_denied_grace_period; return rpc_success; } /* Obtain client and file */ if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; /* Now try to remove the lock */ - resp->status = cast_status(nlmsvc_unlock(file, &argp->lock)); + resp->status = cast_status(nlmsvc_unlock(net, file, &argp->lock)); dprintk("lockd: UNLOCK status %d\n", ntohl(resp->status)); - nlm_release_host(host); + nlmsvc_release_host(host); nlm_release_file(file); return rpc_success; } @@ -249,102 +240,121 @@ nlmsvc_proc_unlock(struct svc_rqst *rqstp, struct nlm_args *argp, * GRANTED: A server calls us to tell that a process' lock request * was granted */ -static int +static __be32 nlmsvc_proc_granted(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { resp->cookie = argp->cookie; dprintk("lockd: GRANTED called\n"); - resp->status = nlmclnt_grant(&argp->lock); + resp->status = nlmclnt_grant(svc_addr(rqstp), &argp->lock); dprintk("lockd: GRANTED status %d\n", ntohl(resp->status)); return rpc_success; } /* + * This is the generic lockd callback for async RPC calls + */ +static void nlmsvc_callback_exit(struct rpc_task *task, void *data) +{ + dprintk("lockd: %5u callback returned %d\n", task->tk_pid, + -task->tk_status); +} + +void nlmsvc_release_call(struct nlm_rqst *call) +{ + if (!atomic_dec_and_test(&call->a_count)) + return; + nlmsvc_release_host(call->a_host); + kfree(call); +} + +static void nlmsvc_callback_release(void *data) +{ + nlmsvc_release_call(data); +} + +static const struct rpc_call_ops nlmsvc_callback_ops = { + .rpc_call_done = nlmsvc_callback_exit, + .rpc_release = nlmsvc_callback_release, +}; + +/* * `Async' versions of the above service routines. They aren't really, * because we send the callback before the reply proper. I hope this * doesn't break any clients. */ -static int -nlmsvc_proc_test_msg(struct svc_rqst *rqstp, struct nlm_args *argp, - void *resp) +static __be32 nlmsvc_callback(struct svc_rqst *rqstp, u32 proc, struct nlm_args *argp, + __be32 (*func)(struct svc_rqst *, struct nlm_args *, struct nlm_res *)) { - struct nlm_res res; - u32 stat; + struct nlm_host *host; + struct nlm_rqst *call; + __be32 stat; - dprintk("lockd: TEST_MSG called\n"); - memset(&res, 0, sizeof(res)); + host = nlmsvc_lookup_host(rqstp, + argp->lock.caller, + argp->lock.len); + if (host == NULL) + return rpc_system_err; + + call = nlm_alloc_call(host); + nlmsvc_release_host(host); + if (call == NULL) + return rpc_system_err; + + stat = func(rqstp, argp, &call->a_res); + if (stat != 0) { + nlmsvc_release_call(call); + return stat; + } - if ((stat = nlmsvc_proc_test(rqstp, argp, &res)) == 0) - stat = nlmsvc_callback(rqstp, NLMPROC_TEST_RES, &res); - return stat; + call->a_flags = RPC_TASK_ASYNC; + if (nlm_async_reply(call, proc, &nlmsvc_callback_ops) < 0) + return rpc_system_err; + return rpc_success; } -static int -nlmsvc_proc_lock_msg(struct svc_rqst *rqstp, struct nlm_args *argp, +static __be32 nlmsvc_proc_test_msg(struct svc_rqst *rqstp, struct nlm_args *argp, void *resp) { - struct nlm_res res; - u32 stat; + dprintk("lockd: TEST_MSG called\n"); + return nlmsvc_callback(rqstp, NLMPROC_TEST_RES, argp, nlmsvc_proc_test); +} +static __be32 nlmsvc_proc_lock_msg(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ dprintk("lockd: LOCK_MSG called\n"); - memset(&res, 0, sizeof(res)); - - if ((stat = nlmsvc_proc_lock(rqstp, argp, &res)) == 0) - stat = nlmsvc_callback(rqstp, NLMPROC_LOCK_RES, &res); - return stat; + return nlmsvc_callback(rqstp, NLMPROC_LOCK_RES, argp, nlmsvc_proc_lock); } -static int -nlmsvc_proc_cancel_msg(struct svc_rqst *rqstp, struct nlm_args *argp, +static __be32 nlmsvc_proc_cancel_msg(struct svc_rqst *rqstp, struct nlm_args *argp, void *resp) { - struct nlm_res res; - u32 stat; - dprintk("lockd: CANCEL_MSG called\n"); - memset(&res, 0, sizeof(res)); - - if ((stat = nlmsvc_proc_cancel(rqstp, argp, &res)) == 0) - stat = nlmsvc_callback(rqstp, NLMPROC_CANCEL_RES, &res); - return stat; + return nlmsvc_callback(rqstp, NLMPROC_CANCEL_RES, argp, nlmsvc_proc_cancel); } -static int +static __be32 nlmsvc_proc_unlock_msg(struct svc_rqst *rqstp, struct nlm_args *argp, void *resp) { - struct nlm_res res; - u32 stat; - dprintk("lockd: UNLOCK_MSG called\n"); - memset(&res, 0, sizeof(res)); - - if ((stat = nlmsvc_proc_unlock(rqstp, argp, &res)) == 0) - stat = nlmsvc_callback(rqstp, NLMPROC_UNLOCK_RES, &res); - return stat; + return nlmsvc_callback(rqstp, NLMPROC_UNLOCK_RES, argp, nlmsvc_proc_unlock); } -static int +static __be32 nlmsvc_proc_granted_msg(struct svc_rqst *rqstp, struct nlm_args *argp, void *resp) { - struct nlm_res res; - u32 stat; - dprintk("lockd: GRANTED_MSG called\n"); - memset(&res, 0, sizeof(res)); - - if ((stat = nlmsvc_proc_granted(rqstp, argp, &res)) == 0) - stat = nlmsvc_callback(rqstp, NLMPROC_GRANTED_RES, &res); - return stat; + return nlmsvc_callback(rqstp, NLMPROC_GRANTED_RES, argp, nlmsvc_proc_granted); } /* * SHARE: create a DOS share or alter existing share. */ -static int +static __be32 nlmsvc_proc_share(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { @@ -356,20 +366,20 @@ nlmsvc_proc_share(struct svc_rqst *rqstp, struct nlm_args *argp, resp->cookie = argp->cookie; /* Don't accept new lock requests during grace period */ - if (nlmsvc_grace_period && !argp->reclaim) { + if (locks_in_grace(SVC_NET(rqstp)) && !argp->reclaim) { resp->status = nlm_lck_denied_grace_period; return rpc_success; } /* Obtain client and file */ if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; /* Now try to create the share */ resp->status = cast_status(nlmsvc_share_file(host, file, argp)); dprintk("lockd: SHARE status %d\n", ntohl(resp->status)); - nlm_release_host(host); + nlmsvc_release_host(host); nlm_release_file(file); return rpc_success; } @@ -377,7 +387,7 @@ nlmsvc_proc_share(struct svc_rqst *rqstp, struct nlm_args *argp, /* * UNSHARE: Release a DOS share. */ -static int +static __be32 nlmsvc_proc_unshare(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { @@ -389,20 +399,20 @@ nlmsvc_proc_unshare(struct svc_rqst *rqstp, struct nlm_args *argp, resp->cookie = argp->cookie; /* Don't accept requests during grace period */ - if (nlmsvc_grace_period) { + if (locks_in_grace(SVC_NET(rqstp))) { resp->status = nlm_lck_denied_grace_period; return rpc_success; } /* Obtain client and file */ if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) - return rpc_success; + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; /* Now try to unshare the file */ resp->status = cast_status(nlmsvc_unshare_file(host, file, argp)); dprintk("lockd: UNSHARE status %d\n", ntohl(resp->status)); - nlm_release_host(host); + nlmsvc_release_host(host); nlm_release_file(file); return rpc_success; } @@ -410,7 +420,7 @@ nlmsvc_proc_unshare(struct svc_rqst *rqstp, struct nlm_args *argp, /* * NM_LOCK: Create an unmonitored lock */ -static int +static __be32 nlmsvc_proc_nm_lock(struct svc_rqst *rqstp, struct nlm_args *argp, struct nlm_res *resp) { @@ -423,7 +433,7 @@ nlmsvc_proc_nm_lock(struct svc_rqst *rqstp, struct nlm_args *argp, /* * FREE_ALL: Release all locks and shares held by client */ -static int +static __be32 nlmsvc_proc_free_all(struct svc_rqst *rqstp, struct nlm_args *argp, void *resp) { @@ -434,56 +444,34 @@ nlmsvc_proc_free_all(struct svc_rqst *rqstp, struct nlm_args *argp, return rpc_success; nlmsvc_free_host_resources(host); - nlm_release_host(host); + nlmsvc_release_host(host); return rpc_success; } /* * SM_NOTIFY: private callback from statd (not part of official NLM proto) */ -static int +static __be32 nlmsvc_proc_sm_notify(struct svc_rqst *rqstp, struct nlm_reboot *argp, void *resp) { - struct sockaddr_in saddr = rqstp->rq_addr; - int vers = argp->vers; - int prot = argp->proto >> 1; - struct nlm_host *host; - dprintk("lockd: SM_NOTIFY called\n"); - if (saddr.sin_addr.s_addr != htonl(INADDR_LOOPBACK) - || ntohs(saddr.sin_port) >= 1024) { - printk(KERN_WARNING - "lockd: rejected NSM callback from %08x:%d\n", - ntohl(rqstp->rq_addr.sin_addr.s_addr), - ntohs(rqstp->rq_addr.sin_port)); - return rpc_system_err; - } - /* Obtain the host pointer for this NFS server and try to - * reclaim all locks we hold on this server. - */ - saddr.sin_addr.s_addr = argp->addr; - if ((argp->proto & 1)==0) { - if ((host = nlmclnt_lookup_host(&saddr, prot, vers)) != NULL) { - nlmclnt_recovery(host, argp->state); - nlm_release_host(host); - } - } else { - /* If we run on an NFS server, delete all locks held by the client */ - if ((host = nlm_lookup_host(1, &saddr, prot, vers)) != NULL) { - nlmsvc_free_host_resources(host); - nlm_release_host(host); - } + if (!nlm_privileged_requester(rqstp)) { + char buf[RPC_MAX_ADDRBUFLEN]; + printk(KERN_WARNING "lockd: rejected NSM callback from %s\n", + svc_print_addr(rqstp, buf, sizeof(buf))); + return rpc_system_err; } + nlm_host_rebooted(argp); return rpc_success; } /* * client sent a GRANTED_RES, let's remove the associated block */ -static int +static __be32 nlmsvc_proc_granted_res(struct svc_rqst *rqstp, struct nlm_res *argp, void *resp) { @@ -492,59 +480,10 @@ nlmsvc_proc_granted_res(struct svc_rqst *rqstp, struct nlm_res *argp, dprintk("lockd: GRANTED_RES called\n"); - nlmsvc_grant_reply(rqstp, &argp->cookie, argp->status); - return rpc_success; -} - -/* - * This is the generic lockd callback for async RPC calls - */ -static u32 -nlmsvc_callback(struct svc_rqst *rqstp, u32 proc, struct nlm_res *resp) -{ - struct nlm_host *host; - struct nlm_rqst *call; - - if (!(call = nlmclnt_alloc_call())) - return rpc_system_err; - - host = nlmclnt_lookup_host(&rqstp->rq_addr, - rqstp->rq_prot, rqstp->rq_vers); - if (!host) { - kfree(call); - return rpc_system_err; - } - - call->a_flags = RPC_TASK_ASYNC; - call->a_host = host; - memcpy(&call->a_args, resp, sizeof(*resp)); - - if (nlmsvc_async_call(call, proc, &nlmsvc_callback_ops) < 0) - goto error; - + nlmsvc_grant_reply(&argp->cookie, argp->status); return rpc_success; - error: - nlm_release_host(host); - kfree(call); - return rpc_system_err; -} - -static void nlmsvc_callback_exit(struct rpc_task *task, void *data) -{ - struct nlm_rqst *call = data; - - if (task->tk_status < 0) { - dprintk("lockd: %4d callback failed (errno = %d)\n", - task->tk_pid, -task->tk_status); - } - nlm_release_host(call->a_host); - kfree(call); } -static const struct rpc_call_ops nlmsvc_callback_ops = { - .rpc_call_done = nlmsvc_callback_exit, -}; - /* * NLM Server procedures. */ diff --git a/fs/lockd/svcshare.c b/fs/lockd/svcshare.c index 4943fb7836c..b0ae0700870 100644 --- a/fs/lockd/svcshare.c +++ b/fs/lockd/svcshare.c @@ -23,7 +23,7 @@ nlm_cmp_owner(struct nlm_share *share, struct xdr_netobj *oh) && !memcmp(share->s_owner.data, oh->data, oh->len); } -u32 +__be32 nlmsvc_share_file(struct nlm_host *host, struct nlm_file *file, struct nlm_args *argp) { @@ -39,7 +39,7 @@ nlmsvc_share_file(struct nlm_host *host, struct nlm_file *file, return nlm_lck_denied; } - share = (struct nlm_share *) kmalloc(sizeof(*share) + oh->len, + share = kmalloc(sizeof(*share) + oh->len, GFP_KERNEL); if (share == NULL) return nlm_lck_denied_nolocks; @@ -64,14 +64,15 @@ update: /* * Delete a share. */ -u32 +__be32 nlmsvc_unshare_file(struct nlm_host *host, struct nlm_file *file, struct nlm_args *argp) { struct nlm_share *share, **shpp; struct xdr_netobj *oh = &argp->lock.oh; - for (shpp = &file->f_shares; (share = *shpp) != 0; shpp = &share->s_next) { + for (shpp = &file->f_shares; (share = *shpp) != NULL; + shpp = &share->s_next) { if (share->s_host == host && nlm_cmp_owner(share, oh)) { *shpp = share->s_next; kfree(share); @@ -85,27 +86,21 @@ nlmsvc_unshare_file(struct nlm_host *host, struct nlm_file *file, } /* - * Traverse all shares for a given file (and host). - * NLM_ACT_CHECK is handled by nlmsvc_inspect_file. + * Traverse all shares for a given file, and delete + * those owned by the given (type of) host */ -int -nlmsvc_traverse_shares(struct nlm_host *host, struct nlm_file *file, int action) +void nlmsvc_traverse_shares(struct nlm_host *host, struct nlm_file *file, + nlm_host_match_fn_t match) { struct nlm_share *share, **shpp; shpp = &file->f_shares; while ((share = *shpp) != NULL) { - if (action == NLM_ACT_MARK) - share->s_host->h_inuse = 1; - else if (action == NLM_ACT_UNLOCK) { - if (host == NULL || host == share->s_host) { - *shpp = share->s_next; - kfree(share); - continue; - } + if (match(share->s_host, host)) { + *shpp = share->s_next; + kfree(share); + continue; } shpp = &share->s_next; } - - return 0; } diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c index 62f4a385177..b6f3b84b6e9 100644 --- a/fs/lockd/svcsubs.c +++ b/fs/lockd/svcsubs.c @@ -6,18 +6,19 @@ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> */ -#include <linux/config.h> #include <linux/types.h> #include <linux/string.h> #include <linux/time.h> #include <linux/in.h> +#include <linux/slab.h> +#include <linux/mutex.h> #include <linux/sunrpc/svc.h> -#include <linux/sunrpc/clnt.h> -#include <linux/nfsd/nfsfh.h> -#include <linux/nfsd/export.h> +#include <linux/sunrpc/addr.h> #include <linux/lockd/lockd.h> #include <linux/lockd/share.h> -#include <linux/lockd/sm_inter.h> +#include <linux/module.h> +#include <linux/mount.h> +#include <uapi/linux/nfs2.h> #define NLMDBG_FACILITY NLMDBG_SVCSUBS @@ -25,10 +26,10 @@ /* * Global file hash table */ -#define FILE_HASH_BITS 5 +#define FILE_HASH_BITS 7 #define FILE_NRHASH (1<<FILE_HASH_BITS) -static struct nlm_file * nlm_files[FILE_NRHASH]; -static DECLARE_MUTEX(nlm_file_sema); +static struct hlist_head nlm_files[FILE_NRHASH]; +static DEFINE_MUTEX(nlm_file_mutex); #ifdef NFSD_DEBUG static inline void nlm_debug_print_fh(char *msg, struct nfs_fh *f) @@ -43,7 +44,7 @@ static inline void nlm_debug_print_fh(char *msg, struct nfs_fh *f) static inline void nlm_debug_print_file(char *msg, struct nlm_file *file) { - struct inode *inode = file->f_file->f_dentry->d_inode; + struct inode *inode = file_inode(file->f_file); dprintk("lockd: %s %s/%ld\n", msg, inode->i_sb->s_id, inode->i_ino); @@ -78,36 +79,36 @@ static inline unsigned int file_hash(struct nfs_fh *f) * This is not quite right, but for now, we assume the client performs * the proper R/W checking. */ -u32 +__be32 nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result, struct nfs_fh *f) { struct nlm_file *file; unsigned int hash; - u32 nfserr; + __be32 nfserr; - nlm_debug_print_fh("nlm_file_lookup", f); + nlm_debug_print_fh("nlm_lookup_file", f); hash = file_hash(f); /* Lock file table */ - down(&nlm_file_sema); + mutex_lock(&nlm_file_mutex); - for (file = nlm_files[hash]; file; file = file->f_next) + hlist_for_each_entry(file, &nlm_files[hash], f_list) if (!nfs_compare_fh(&file->f_handle, f)) goto found; nlm_debug_print_fh("creating file for", f); nfserr = nlm_lck_denied_nolocks; - file = (struct nlm_file *) kmalloc(sizeof(*file), GFP_KERNEL); + file = kzalloc(sizeof(*file), GFP_KERNEL); if (!file) goto out_unlock; - memset(file, 0, sizeof(*file)); memcpy(&file->f_handle, f, sizeof(struct nfs_fh)); - file->f_hash = hash; - init_MUTEX(&file->f_sema); + mutex_init(&file->f_mutex); + INIT_HLIST_NODE(&file->f_list); + INIT_LIST_HEAD(&file->f_blocks); /* Open the file. Note that this must not sleep for too long, else * we would lock up lockd:-) So no NFS re-exports, folks. @@ -116,12 +117,11 @@ nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result, * the file. */ if ((nfserr = nlmsvc_ops->fopen(rqstp, f, &file->f_file)) != 0) { - dprintk("lockd: open failed (nfserr %d)\n", ntohl(nfserr)); + dprintk("lockd: open failed (error %d)\n", nfserr); goto out_free; } - file->f_next = nlm_files[hash]; - nlm_files[hash] = file; + hlist_add_head(&file->f_list, &nlm_files[hash]); found: dprintk("lockd: found file %p (count %d)\n", file, file->f_count); @@ -130,17 +130,11 @@ found: nfserr = 0; out_unlock: - up(&nlm_file_sema); + mutex_unlock(&nlm_file_mutex); return nfserr; out_free: kfree(file); -#ifdef CONFIG_LOCKD_V4 - if (nfserr == 1) - nfserr = nlm4_stale_fh; - else -#endif - nfserr = nlm_lck_denied; goto out_unlock; } @@ -150,22 +144,14 @@ out_free: static inline void nlm_delete_file(struct nlm_file *file) { - struct nlm_file **fp, *f; - nlm_debug_print_file("closing file", file); - - fp = nlm_files + file->f_hash; - while ((f = *fp) != NULL) { - if (f == file) { - *fp = file->f_next; - nlmsvc_ops->fclose(file->f_file); - kfree(file); - return; - } - fp = &f->f_next; + if (!hlist_unhashed(&file->f_list)) { + hlist_del(&file->f_list); + nlmsvc_ops->fclose(file->f_file); + kfree(file); + } else { + printk(KERN_WARNING "lockd: attempt to release unknown file!\n"); } - - printk(KERN_WARNING "lockd: attempt to release unknown file!\n"); } /* @@ -173,7 +159,8 @@ nlm_delete_file(struct nlm_file *file) * action. */ static int -nlm_traverse_locks(struct nlm_host *host, struct nlm_file *file, int action) +nlm_traverse_locks(struct nlm_host *host, struct nlm_file *file, + nlm_host_match_fn_t match) { struct inode *inode = nlmsvc_file_inode(file); struct file_lock *fl; @@ -181,27 +168,23 @@ nlm_traverse_locks(struct nlm_host *host, struct nlm_file *file, int action) again: file->f_locks = 0; + spin_lock(&inode->i_lock); for (fl = inode->i_flock; fl; fl = fl->fl_next) { - if (!(fl->fl_flags & FL_LOCKD)) + if (fl->fl_lmops != &nlmsvc_lock_operations) continue; /* update current lock count */ file->f_locks++; + lockhost = (struct nlm_host *) fl->fl_owner; - if (action == NLM_ACT_MARK) - lockhost->h_inuse = 1; - else if (action == NLM_ACT_CHECK) - return 1; - else if (action == NLM_ACT_UNLOCK) { + if (match(lockhost, host)) { struct file_lock lock = *fl; - if (host && lockhost != host) - continue; - + spin_unlock(&inode->i_lock); lock.fl_type = F_UNLCK; lock.fl_start = 0; lock.fl_end = OFFSET_MAX; - if (posix_lock_file(file->f_file, &lock) < 0) { + if (vfs_lock_file(file->f_file, F_SETLK, &lock, NULL) < 0) { printk("lockd: unlock failure in %s:%d\n", __FILE__, __LINE__); return 1; @@ -209,61 +192,90 @@ again: goto again; } } + spin_unlock(&inode->i_lock); return 0; } +static int +nlmsvc_always_match(void *dummy1, struct nlm_host *dummy2) +{ + return 1; +} + /* - * Operate on a single file + * Inspect a single file */ static inline int -nlm_inspect_file(struct nlm_host *host, struct nlm_file *file, int action) +nlm_inspect_file(struct nlm_host *host, struct nlm_file *file, nlm_host_match_fn_t match) { - if (action == NLM_ACT_CHECK) { - /* Fast path for mark and sweep garbage collection */ - if (file->f_count || file->f_blocks || file->f_shares) - return 1; - } else { - if (nlmsvc_traverse_blocks(host, file, action) - || nlmsvc_traverse_shares(host, file, action)) + nlmsvc_traverse_blocks(host, file, match); + nlmsvc_traverse_shares(host, file, match); + return nlm_traverse_locks(host, file, match); +} + +/* + * Quick check whether there are still any locks, blocks or + * shares on a given file. + */ +static inline int +nlm_file_inuse(struct nlm_file *file) +{ + struct inode *inode = nlmsvc_file_inode(file); + struct file_lock *fl; + + if (file->f_count || !list_empty(&file->f_blocks) || file->f_shares) + return 1; + + spin_lock(&inode->i_lock); + for (fl = inode->i_flock; fl; fl = fl->fl_next) { + if (fl->fl_lmops == &nlmsvc_lock_operations) { + spin_unlock(&inode->i_lock); return 1; + } } - return nlm_traverse_locks(host, file, action); + spin_unlock(&inode->i_lock); + file->f_locks = 0; + return 0; } /* * Loop over all files in the file table. */ static int -nlm_traverse_files(struct nlm_host *host, int action) +nlm_traverse_files(void *data, nlm_host_match_fn_t match, + int (*is_failover_file)(void *data, struct nlm_file *file)) { - struct nlm_file *file, **fp; - int i; + struct hlist_node *next; + struct nlm_file *file; + int i, ret = 0; - down(&nlm_file_sema); + mutex_lock(&nlm_file_mutex); for (i = 0; i < FILE_NRHASH; i++) { - fp = nlm_files + i; - while ((file = *fp) != NULL) { + hlist_for_each_entry_safe(file, next, &nlm_files[i], f_list) { + if (is_failover_file && !is_failover_file(data, file)) + continue; + file->f_count++; + mutex_unlock(&nlm_file_mutex); + /* Traverse locks, blocks and shares of this file * and update file->f_locks count */ - if (nlm_inspect_file(host, file, action)) { - up(&nlm_file_sema); - return 1; - } + if (nlm_inspect_file(data, file, match)) + ret = 1; + mutex_lock(&nlm_file_mutex); + file->f_count--; /* No more references to this file. Let go of it. */ - if (!file->f_blocks && !file->f_locks + if (list_empty(&file->f_blocks) && !file->f_locks && !file->f_shares && !file->f_count) { - *fp = file->f_next; + hlist_del(&file->f_list); nlmsvc_ops->fclose(file->f_file); kfree(file); - } else { - fp = &file->f_next; } } } - up(&nlm_file_sema); - return 0; + mutex_unlock(&nlm_file_mutex); + return ret; } /* @@ -282,26 +294,79 @@ nlm_release_file(struct nlm_file *file) file, file->f_count); /* Lock file table */ - down(&nlm_file_sema); + mutex_lock(&nlm_file_mutex); /* If there are no more locks etc, delete the file */ - if(--file->f_count == 0) { - if(!nlm_inspect_file(NULL, file, NLM_ACT_CHECK)) - nlm_delete_file(file); - } + if (--file->f_count == 0 && !nlm_file_inuse(file)) + nlm_delete_file(file); - up(&nlm_file_sema); + mutex_unlock(&nlm_file_mutex); +} + +/* + * Helpers function for resource traversal + * + * nlmsvc_mark_host: + * used by the garbage collector; simply sets h_inuse only for those + * hosts, which passed network check. + * Always returns 0. + * + * nlmsvc_same_host: + * returns 1 iff the two hosts match. Used to release + * all resources bound to a specific host. + * + * nlmsvc_is_client: + * returns 1 iff the host is a client. + * Used by nlmsvc_invalidate_all + */ + +static int +nlmsvc_mark_host(void *data, struct nlm_host *hint) +{ + struct nlm_host *host = data; + + if ((hint->net == NULL) || + (host->net == hint->net)) + host->h_inuse = 1; + return 0; +} + +static int +nlmsvc_same_host(void *data, struct nlm_host *other) +{ + struct nlm_host *host = data; + + return host == other; +} + +static int +nlmsvc_is_client(void *data, struct nlm_host *dummy) +{ + struct nlm_host *host = data; + + if (host->h_server) { + /* we are destroying locks even though the client + * hasn't asked us too, so don't unmonitor the + * client + */ + if (host->h_nsmhandle) + host->h_nsmhandle->sm_sticky = 1; + return 1; + } else + return 0; } /* * Mark all hosts that still hold resources */ void -nlmsvc_mark_resources(void) +nlmsvc_mark_resources(struct net *net) { - dprintk("lockd: nlmsvc_mark_resources\n"); + struct nlm_host hint; - nlm_traverse_files(NULL, NLM_ACT_MARK); + dprintk("lockd: nlmsvc_mark_resources for net %p\n", net); + hint.net = net; + nlm_traverse_files(&hint, nlmsvc_mark_host, NULL); } /* @@ -312,23 +377,75 @@ nlmsvc_free_host_resources(struct nlm_host *host) { dprintk("lockd: nlmsvc_free_host_resources\n"); - if (nlm_traverse_files(host, NLM_ACT_UNLOCK)) + if (nlm_traverse_files(host, nlmsvc_same_host, NULL)) { printk(KERN_WARNING - "lockd: couldn't remove all locks held by %s", + "lockd: couldn't remove all locks held by %s\n", host->h_name); + BUG(); + } } -/* - * delete all hosts structs for clients +/** + * nlmsvc_invalidate_all - remove all locks held for clients + * + * Release all locks held by NFS clients. + * */ void nlmsvc_invalidate_all(void) { - struct nlm_host *host; - while ((host = nlm_find_client()) != NULL) { - nlmsvc_free_host_resources(host); - host->h_expires = 0; - host->h_killed = 1; - nlm_release_host(host); - } + /* + * Previously, the code would call + * nlmsvc_free_host_resources for each client in + * turn, which is about as inefficient as it gets. + * Now we just do it once in nlm_traverse_files. + */ + nlm_traverse_files(NULL, nlmsvc_is_client, NULL); +} + +static int +nlmsvc_match_sb(void *datap, struct nlm_file *file) +{ + struct super_block *sb = datap; + + return sb == file->f_file->f_path.dentry->d_sb; +} + +/** + * nlmsvc_unlock_all_by_sb - release locks held on this file system + * @sb: super block + * + * Release all locks held by clients accessing this file system. + */ +int +nlmsvc_unlock_all_by_sb(struct super_block *sb) +{ + int ret; + + ret = nlm_traverse_files(sb, nlmsvc_always_match, nlmsvc_match_sb); + return ret ? -EIO : 0; +} +EXPORT_SYMBOL_GPL(nlmsvc_unlock_all_by_sb); + +static int +nlmsvc_match_ip(void *datap, struct nlm_host *host) +{ + return rpc_cmp_addr(nlm_srcaddr(host), datap); +} + +/** + * nlmsvc_unlock_all_by_ip - release local locks by IP address + * @server_addr: server's IP address as seen by clients + * + * Release all locks held by clients accessing this host + * via the passed in IP address. + */ +int +nlmsvc_unlock_all_by_ip(struct sockaddr *server_addr) +{ + int ret; + + ret = nlm_traverse_files(server_addr, nlmsvc_match_ip, NULL); + return ret ? -EIO : 0; } +EXPORT_SYMBOL_GPL(nlmsvc_unlock_all_by_ip); diff --git a/fs/lockd/xdr.c b/fs/lockd/xdr.c index 200fbda2c6d..9340e7e10ef 100644 --- a/fs/lockd/xdr.c +++ b/fs/lockd/xdr.c @@ -6,10 +6,8 @@ * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> */ -#include <linux/config.h> #include <linux/types.h> #include <linux/sched.h> -#include <linux/utsname.h> #include <linux/nfs.h> #include <linux/sunrpc/xdr.h> @@ -17,7 +15,8 @@ #include <linux/sunrpc/svc.h> #include <linux/sunrpc/stats.h> #include <linux/lockd/lockd.h> -#include <linux/lockd/sm_inter.h> + +#include <uapi/linux/nfs2.h> #define NLMDBG_FACILITY NLMDBG_XDR @@ -44,7 +43,7 @@ loff_t_to_s32(loff_t offset) /* * XDR functions for basic NLM types */ -static u32 *nlm_decode_cookie(u32 *p, struct nlm_cookie *c) +static __be32 *nlm_decode_cookie(__be32 *p, struct nlm_cookie *c) { unsigned int len; @@ -63,15 +62,16 @@ static u32 *nlm_decode_cookie(u32 *p, struct nlm_cookie *c) } else { - printk(KERN_NOTICE - "lockd: bad cookie size %d (only cookies under %d bytes are supported.)\n", len, NLM_MAXCOOKIELEN); + dprintk("lockd: bad cookie size %d (only cookies under " + "%d bytes are supported.)\n", + len, NLM_MAXCOOKIELEN); return NULL; } return p; } -static inline u32 * -nlm_encode_cookie(u32 *p, struct nlm_cookie *c) +static inline __be32 * +nlm_encode_cookie(__be32 *p, struct nlm_cookie *c) { *p++ = htonl(c->len); memcpy(p, c->data, c->len); @@ -79,14 +79,13 @@ nlm_encode_cookie(u32 *p, struct nlm_cookie *c) return p; } -static u32 * -nlm_decode_fh(u32 *p, struct nfs_fh *f) +static __be32 * +nlm_decode_fh(__be32 *p, struct nfs_fh *f) { unsigned int len; if ((len = ntohl(*p++)) != NFS2_FHSIZE) { - printk(KERN_NOTICE - "lockd: bad fhandle size %d (should be %d)\n", + dprintk("lockd: bad fhandle size %d (should be %d)\n", len, NFS2_FHSIZE); return NULL; } @@ -96,8 +95,8 @@ nlm_decode_fh(u32 *p, struct nfs_fh *f) return p + XDR_QUADLEN(NFS2_FHSIZE); } -static inline u32 * -nlm_encode_fh(u32 *p, struct nfs_fh *f) +static inline __be32 * +nlm_encode_fh(__be32 *p, struct nfs_fh *f) { *p++ = htonl(NFS2_FHSIZE); memcpy(p, f->data, NFS2_FHSIZE); @@ -107,20 +106,20 @@ nlm_encode_fh(u32 *p, struct nfs_fh *f) /* * Encode and decode owner handle */ -static inline u32 * -nlm_decode_oh(u32 *p, struct xdr_netobj *oh) +static inline __be32 * +nlm_decode_oh(__be32 *p, struct xdr_netobj *oh) { return xdr_decode_netobj(p, oh); } -static inline u32 * -nlm_encode_oh(u32 *p, struct xdr_netobj *oh) +static inline __be32 * +nlm_encode_oh(__be32 *p, struct xdr_netobj *oh) { return xdr_encode_netobj(p, oh); } -static u32 * -nlm_decode_lock(u32 *p, struct nlm_lock *lock) +static __be32 * +nlm_decode_lock(__be32 *p, struct nlm_lock *lock) { struct file_lock *fl = &lock->fl; s32 start, len, end; @@ -131,10 +130,11 @@ nlm_decode_lock(u32 *p, struct nlm_lock *lock) || !(p = nlm_decode_fh(p, &lock->fh)) || !(p = nlm_decode_oh(p, &lock->oh))) return NULL; + lock->svid = ntohl(*p++); locks_init_lock(fl); fl->fl_owner = current->files; - fl->fl_pid = ntohl(*p++); + fl->fl_pid = (pid_t)lock->svid; fl->fl_flags = FL_POSIX; fl->fl_type = F_RDLCK; /* as good as anything else */ start = ntohl(*p++); @@ -151,41 +151,10 @@ nlm_decode_lock(u32 *p, struct nlm_lock *lock) } /* - * Encode a lock as part of an NLM call - */ -static u32 * -nlm_encode_lock(u32 *p, struct nlm_lock *lock) -{ - struct file_lock *fl = &lock->fl; - __s32 start, len; - - if (!(p = xdr_encode_string(p, lock->caller)) - || !(p = nlm_encode_fh(p, &lock->fh)) - || !(p = nlm_encode_oh(p, &lock->oh))) - return NULL; - - if (fl->fl_start > NLM_OFFSET_MAX - || (fl->fl_end > NLM_OFFSET_MAX && fl->fl_end != OFFSET_MAX)) - return NULL; - - start = loff_t_to_s32(fl->fl_start); - if (fl->fl_end == OFFSET_MAX) - len = 0; - else - len = loff_t_to_s32(fl->fl_end - fl->fl_start + 1); - - *p++ = htonl(fl->fl_pid); - *p++ = htonl(start); - *p++ = htonl(len); - - return p; -} - -/* * Encode result of a TEST/TEST_MSG call */ -static u32 * -nlm_encode_testres(u32 *p, struct nlm_res *resp) +static __be32 * +nlm_encode_testres(__be32 *p, struct nlm_res *resp) { s32 start, len; @@ -197,7 +166,7 @@ nlm_encode_testres(u32 *p, struct nlm_res *resp) struct file_lock *fl = &resp->lock.fl; *p++ = (fl->fl_type == F_RDLCK)? xdr_zero : xdr_one; - *p++ = htonl(fl->fl_pid); + *p++ = htonl(resp->lock.svid); /* Encode owner handle. */ if (!(p = xdr_encode_netobj(p, &resp->lock.oh))) @@ -221,7 +190,7 @@ nlm_encode_testres(u32 *p, struct nlm_res *resp) * First, the server side XDR functions */ int -nlmsvc_decode_testargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) +nlmsvc_decode_testargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) { u32 exclusive; @@ -238,7 +207,7 @@ nlmsvc_decode_testargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) } int -nlmsvc_encode_testres(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) +nlmsvc_encode_testres(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) { if (!(p = nlm_encode_testres(p, resp))) return 0; @@ -246,7 +215,7 @@ nlmsvc_encode_testres(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) } int -nlmsvc_decode_lockargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) +nlmsvc_decode_lockargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) { u32 exclusive; @@ -266,7 +235,7 @@ nlmsvc_decode_lockargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) } int -nlmsvc_decode_cancargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) +nlmsvc_decode_cancargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) { u32 exclusive; @@ -282,7 +251,7 @@ nlmsvc_decode_cancargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) } int -nlmsvc_decode_unlockargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) +nlmsvc_decode_unlockargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) { if (!(p = nlm_decode_cookie(p, &argp->cookie)) || !(p = nlm_decode_lock(p, &argp->lock))) @@ -292,13 +261,14 @@ nlmsvc_decode_unlockargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) } int -nlmsvc_decode_shareargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) +nlmsvc_decode_shareargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) { struct nlm_lock *lock = &argp->lock; memset(lock, 0, sizeof(*lock)); locks_init_lock(&lock->fl); - lock->fl.fl_pid = ~(u32) 0; + lock->svid = ~(u32) 0; + lock->fl.fl_pid = (pid_t)lock->svid; if (!(p = nlm_decode_cookie(p, &argp->cookie)) || !(p = xdr_decode_string_inplace(p, &lock->caller, @@ -312,7 +282,7 @@ nlmsvc_decode_shareargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) } int -nlmsvc_encode_shareres(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) +nlmsvc_encode_shareres(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) { if (!(p = nlm_encode_cookie(p, &resp->cookie))) return 0; @@ -322,7 +292,7 @@ nlmsvc_encode_shareres(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) } int -nlmsvc_encode_res(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) +nlmsvc_encode_res(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) { if (!(p = nlm_encode_cookie(p, &resp->cookie))) return 0; @@ -331,7 +301,7 @@ nlmsvc_encode_res(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) } int -nlmsvc_decode_notify(struct svc_rqst *rqstp, u32 *p, struct nlm_args *argp) +nlmsvc_decode_notify(struct svc_rqst *rqstp, __be32 *p, struct nlm_args *argp) { struct nlm_lock *lock = &argp->lock; @@ -343,293 +313,33 @@ nlmsvc_decode_notify(struct svc_rqst *rqstp, u32 *p, struct nlm_args *argp) } int -nlmsvc_decode_reboot(struct svc_rqst *rqstp, u32 *p, struct nlm_reboot *argp) +nlmsvc_decode_reboot(struct svc_rqst *rqstp, __be32 *p, struct nlm_reboot *argp) { if (!(p = xdr_decode_string_inplace(p, &argp->mon, &argp->len, SM_MAXSTRLEN))) return 0; argp->state = ntohl(*p++); - /* Preserve the address in network byte order */ - argp->addr = *p++; - argp->vers = *p++; - argp->proto = *p++; + memcpy(&argp->priv.data, p, sizeof(argp->priv.data)); + p += XDR_QUADLEN(SM_PRIV_SIZE); return xdr_argsize_check(rqstp, p); } int -nlmsvc_decode_res(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) +nlmsvc_decode_res(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) { if (!(p = nlm_decode_cookie(p, &resp->cookie))) return 0; - resp->status = ntohl(*p++); + resp->status = *p++; return xdr_argsize_check(rqstp, p); } int -nlmsvc_decode_void(struct svc_rqst *rqstp, u32 *p, void *dummy) +nlmsvc_decode_void(struct svc_rqst *rqstp, __be32 *p, void *dummy) { return xdr_argsize_check(rqstp, p); } int -nlmsvc_encode_void(struct svc_rqst *rqstp, u32 *p, void *dummy) +nlmsvc_encode_void(struct svc_rqst *rqstp, __be32 *p, void *dummy) { return xdr_ressize_check(rqstp, p); } - -/* - * Now, the client side XDR functions - */ -#ifdef NLMCLNT_SUPPORT_SHARES -static int -nlmclt_decode_void(struct rpc_rqst *req, u32 *p, void *ptr) -{ - return 0; -} -#endif - -static int -nlmclt_encode_testargs(struct rpc_rqst *req, u32 *p, nlm_args *argp) -{ - struct nlm_lock *lock = &argp->lock; - - if (!(p = nlm_encode_cookie(p, &argp->cookie))) - return -EIO; - *p++ = (lock->fl.fl_type == F_WRLCK)? xdr_one : xdr_zero; - if (!(p = nlm_encode_lock(p, lock))) - return -EIO; - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlmclt_decode_testres(struct rpc_rqst *req, u32 *p, struct nlm_res *resp) -{ - if (!(p = nlm_decode_cookie(p, &resp->cookie))) - return -EIO; - resp->status = ntohl(*p++); - if (resp->status == NLM_LCK_DENIED) { - struct file_lock *fl = &resp->lock.fl; - u32 excl; - s32 start, len, end; - - memset(&resp->lock, 0, sizeof(resp->lock)); - locks_init_lock(fl); - excl = ntohl(*p++); - fl->fl_pid = ntohl(*p++); - if (!(p = nlm_decode_oh(p, &resp->lock.oh))) - return -EIO; - - fl->fl_flags = FL_POSIX; - fl->fl_type = excl? F_WRLCK : F_RDLCK; - start = ntohl(*p++); - len = ntohl(*p++); - end = start + len - 1; - - fl->fl_start = s32_to_loff_t(start); - if (len == 0 || end < 0) - fl->fl_end = OFFSET_MAX; - else - fl->fl_end = s32_to_loff_t(end); - } - return 0; -} - - -static int -nlmclt_encode_lockargs(struct rpc_rqst *req, u32 *p, nlm_args *argp) -{ - struct nlm_lock *lock = &argp->lock; - - if (!(p = nlm_encode_cookie(p, &argp->cookie))) - return -EIO; - *p++ = argp->block? xdr_one : xdr_zero; - *p++ = (lock->fl.fl_type == F_WRLCK)? xdr_one : xdr_zero; - if (!(p = nlm_encode_lock(p, lock))) - return -EIO; - *p++ = argp->reclaim? xdr_one : xdr_zero; - *p++ = htonl(argp->state); - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlmclt_encode_cancargs(struct rpc_rqst *req, u32 *p, nlm_args *argp) -{ - struct nlm_lock *lock = &argp->lock; - - if (!(p = nlm_encode_cookie(p, &argp->cookie))) - return -EIO; - *p++ = argp->block? xdr_one : xdr_zero; - *p++ = (lock->fl.fl_type == F_WRLCK)? xdr_one : xdr_zero; - if (!(p = nlm_encode_lock(p, lock))) - return -EIO; - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlmclt_encode_unlockargs(struct rpc_rqst *req, u32 *p, nlm_args *argp) -{ - struct nlm_lock *lock = &argp->lock; - - if (!(p = nlm_encode_cookie(p, &argp->cookie))) - return -EIO; - if (!(p = nlm_encode_lock(p, lock))) - return -EIO; - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlmclt_encode_res(struct rpc_rqst *req, u32 *p, struct nlm_res *resp) -{ - if (!(p = nlm_encode_cookie(p, &resp->cookie))) - return -EIO; - *p++ = resp->status; - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlmclt_encode_testres(struct rpc_rqst *req, u32 *p, struct nlm_res *resp) -{ - if (!(p = nlm_encode_testres(p, resp))) - return -EIO; - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlmclt_decode_res(struct rpc_rqst *req, u32 *p, struct nlm_res *resp) -{ - if (!(p = nlm_decode_cookie(p, &resp->cookie))) - return -EIO; - resp->status = ntohl(*p++); - return 0; -} - -/* - * Buffer requirements for NLM - */ -#define NLM_void_sz 0 -#define NLM_cookie_sz 1+XDR_QUADLEN(NLM_MAXCOOKIELEN) -#define NLM_caller_sz 1+XDR_QUADLEN(sizeof(system_utsname.nodename)) -#define NLM_netobj_sz 1+XDR_QUADLEN(XDR_MAX_NETOBJ) -/* #define NLM_owner_sz 1+XDR_QUADLEN(NLM_MAXOWNER) */ -#define NLM_fhandle_sz 1+XDR_QUADLEN(NFS2_FHSIZE) -#define NLM_lock_sz 3+NLM_caller_sz+NLM_netobj_sz+NLM_fhandle_sz -#define NLM_holder_sz 4+NLM_netobj_sz - -#define NLM_testargs_sz NLM_cookie_sz+1+NLM_lock_sz -#define NLM_lockargs_sz NLM_cookie_sz+4+NLM_lock_sz -#define NLM_cancargs_sz NLM_cookie_sz+2+NLM_lock_sz -#define NLM_unlockargs_sz NLM_cookie_sz+NLM_lock_sz - -#define NLM_testres_sz NLM_cookie_sz+1+NLM_holder_sz -#define NLM_res_sz NLM_cookie_sz+1 -#define NLM_norep_sz 0 - -#ifndef MAX -# define MAX(a, b) (((a) > (b))? (a) : (b)) -#endif - -/* - * For NLM, a void procedure really returns nothing - */ -#define nlmclt_decode_norep NULL - -#define PROC(proc, argtype, restype) \ -[NLMPROC_##proc] = { \ - .p_proc = NLMPROC_##proc, \ - .p_encode = (kxdrproc_t) nlmclt_encode_##argtype, \ - .p_decode = (kxdrproc_t) nlmclt_decode_##restype, \ - .p_bufsiz = MAX(NLM_##argtype##_sz, NLM_##restype##_sz) << 2 \ - } - -static struct rpc_procinfo nlm_procedures[] = { - PROC(TEST, testargs, testres), - PROC(LOCK, lockargs, res), - PROC(CANCEL, cancargs, res), - PROC(UNLOCK, unlockargs, res), - PROC(GRANTED, testargs, res), - PROC(TEST_MSG, testargs, norep), - PROC(LOCK_MSG, lockargs, norep), - PROC(CANCEL_MSG, cancargs, norep), - PROC(UNLOCK_MSG, unlockargs, norep), - PROC(GRANTED_MSG, testargs, norep), - PROC(TEST_RES, testres, norep), - PROC(LOCK_RES, res, norep), - PROC(CANCEL_RES, res, norep), - PROC(UNLOCK_RES, res, norep), - PROC(GRANTED_RES, res, norep), -#ifdef NLMCLNT_SUPPORT_SHARES - PROC(SHARE, shareargs, shareres), - PROC(UNSHARE, shareargs, shareres), - PROC(NM_LOCK, lockargs, res), - PROC(FREE_ALL, notify, void), -#endif -}; - -static struct rpc_version nlm_version1 = { - .number = 1, - .nrprocs = 16, - .procs = nlm_procedures, -}; - -static struct rpc_version nlm_version3 = { - .number = 3, - .nrprocs = 24, - .procs = nlm_procedures, -}; - -#ifdef CONFIG_LOCKD_V4 -extern struct rpc_version nlm_version4; -#endif - -static struct rpc_version * nlm_versions[] = { - [1] = &nlm_version1, - [3] = &nlm_version3, -#ifdef CONFIG_LOCKD_V4 - [4] = &nlm_version4, -#endif -}; - -static struct rpc_stat nlm_stats; - -struct rpc_program nlm_program = { - .name = "lockd", - .number = NLM_PROGRAM, - .nrvers = sizeof(nlm_versions) / sizeof(nlm_versions[0]), - .version = nlm_versions, - .stats = &nlm_stats, -}; - -#ifdef RPC_DEBUG -const char *nlmdbg_cookie2a(const struct nlm_cookie *cookie) -{ - /* - * We can get away with a static buffer because we're only - * called with BKL held. - */ - static char buf[2*NLM_MAXCOOKIELEN+1]; - int i; - int len = sizeof(buf); - char *p = buf; - - len--; /* allow for trailing \0 */ - if (len < 3) - return "???"; - for (i = 0 ; i < cookie->len ; i++) { - if (len < 2) { - strcpy(p-3, "..."); - break; - } - sprintf(p, "%02x", cookie->data[i]); - p += 2; - len -= 2; - } - *p = '\0'; - - return buf; -} -#endif diff --git a/fs/lockd/xdr4.c b/fs/lockd/xdr4.c index fdcf105a530..dfa4789cd46 100644 --- a/fs/lockd/xdr4.c +++ b/fs/lockd/xdr4.c @@ -9,7 +9,6 @@ #include <linux/types.h> #include <linux/sched.h> -#include <linux/utsname.h> #include <linux/nfs.h> #include <linux/sunrpc/xdr.h> @@ -17,7 +16,6 @@ #include <linux/sunrpc/svc.h> #include <linux/sunrpc/stats.h> #include <linux/lockd/lockd.h> -#include <linux/lockd/sm_inter.h> #define NLMDBG_FACILITY NLMDBG_XDR @@ -44,8 +42,8 @@ loff_t_to_s64(loff_t offset) /* * XDR functions for basic NLM types */ -static u32 * -nlm4_decode_cookie(u32 *p, struct nlm_cookie *c) +static __be32 * +nlm4_decode_cookie(__be32 *p, struct nlm_cookie *c) { unsigned int len; @@ -64,15 +62,16 @@ nlm4_decode_cookie(u32 *p, struct nlm_cookie *c) } else { - printk(KERN_NOTICE - "lockd: bad cookie size %d (only cookies under %d bytes are supported.)\n", len, NLM_MAXCOOKIELEN); + dprintk("lockd: bad cookie size %d (only cookies under " + "%d bytes are supported.)\n", + len, NLM_MAXCOOKIELEN); return NULL; } return p; } -static u32 * -nlm4_encode_cookie(u32 *p, struct nlm_cookie *c) +static __be32 * +nlm4_encode_cookie(__be32 *p, struct nlm_cookie *c) { *p++ = htonl(c->len); memcpy(p, c->data, c->len); @@ -80,14 +79,13 @@ nlm4_encode_cookie(u32 *p, struct nlm_cookie *c) return p; } -static u32 * -nlm4_decode_fh(u32 *p, struct nfs_fh *f) +static __be32 * +nlm4_decode_fh(__be32 *p, struct nfs_fh *f) { memset(f->data, 0, sizeof(f->data)); f->size = ntohl(*p++); if (f->size > NFS_MAXFHSIZE) { - printk(KERN_NOTICE - "lockd: bad fhandle size %d (should be <=%d)\n", + dprintk("lockd: bad fhandle size %d (should be <=%d)\n", f->size, NFS_MAXFHSIZE); return NULL; } @@ -95,45 +93,32 @@ nlm4_decode_fh(u32 *p, struct nfs_fh *f) return p + XDR_QUADLEN(f->size); } -static u32 * -nlm4_encode_fh(u32 *p, struct nfs_fh *f) -{ - *p++ = htonl(f->size); - if (f->size) p[XDR_QUADLEN(f->size)-1] = 0; /* don't leak anything */ - memcpy(p, f->data, f->size); - return p + XDR_QUADLEN(f->size); -} - /* * Encode and decode owner handle */ -static u32 * -nlm4_decode_oh(u32 *p, struct xdr_netobj *oh) +static __be32 * +nlm4_decode_oh(__be32 *p, struct xdr_netobj *oh) { return xdr_decode_netobj(p, oh); } -static u32 * -nlm4_encode_oh(u32 *p, struct xdr_netobj *oh) -{ - return xdr_encode_netobj(p, oh); -} - -static u32 * -nlm4_decode_lock(u32 *p, struct nlm_lock *lock) +static __be32 * +nlm4_decode_lock(__be32 *p, struct nlm_lock *lock) { struct file_lock *fl = &lock->fl; - __s64 len, start, end; + __u64 len, start; + __s64 end; if (!(p = xdr_decode_string_inplace(p, &lock->caller, &lock->len, NLM_MAXSTRLEN)) || !(p = nlm4_decode_fh(p, &lock->fh)) || !(p = nlm4_decode_oh(p, &lock->oh))) return NULL; + lock->svid = ntohl(*p++); locks_init_lock(fl); fl->fl_owner = current->files; - fl->fl_pid = ntohl(*p++); + fl->fl_pid = (pid_t)lock->svid; fl->fl_flags = FL_POSIX; fl->fl_type = F_RDLCK; /* as good as anything else */ p = xdr_decode_hyper(p, &start); @@ -150,42 +135,10 @@ nlm4_decode_lock(u32 *p, struct nlm_lock *lock) } /* - * Encode a lock as part of an NLM call - */ -static u32 * -nlm4_encode_lock(u32 *p, struct nlm_lock *lock) -{ - struct file_lock *fl = &lock->fl; - __s64 start, len; - - if (!(p = xdr_encode_string(p, lock->caller)) - || !(p = nlm4_encode_fh(p, &lock->fh)) - || !(p = nlm4_encode_oh(p, &lock->oh))) - return NULL; - - if (fl->fl_start > NLM4_OFFSET_MAX - || (fl->fl_end > NLM4_OFFSET_MAX && fl->fl_end != OFFSET_MAX)) - return NULL; - - *p++ = htonl(fl->fl_pid); - - start = loff_t_to_s64(fl->fl_start); - if (fl->fl_end == OFFSET_MAX) - len = 0; - else - len = loff_t_to_s64(fl->fl_end - fl->fl_start + 1); - - p = xdr_encode_hyper(p, start); - p = xdr_encode_hyper(p, len); - - return p; -} - -/* * Encode result of a TEST/TEST_MSG call */ -static u32 * -nlm4_encode_testres(u32 *p, struct nlm_res *resp) +static __be32 * +nlm4_encode_testres(__be32 *p, struct nlm_res *resp) { s64 start, len; @@ -198,7 +151,7 @@ nlm4_encode_testres(u32 *p, struct nlm_res *resp) struct file_lock *fl = &resp->lock.fl; *p++ = (fl->fl_type == F_RDLCK)? xdr_zero : xdr_one; - *p++ = htonl(fl->fl_pid); + *p++ = htonl(resp->lock.svid); /* Encode owner handle. */ if (!(p = xdr_encode_netobj(p, &resp->lock.oh))) @@ -212,8 +165,8 @@ nlm4_encode_testres(u32 *p, struct nlm_res *resp) p = xdr_encode_hyper(p, start); p = xdr_encode_hyper(p, len); - dprintk("xdr: encode_testres (status %d pid %d type %d start %Ld end %Ld)\n", - resp->status, fl->fl_pid, fl->fl_type, + dprintk("xdr: encode_testres (status %u pid %d type %d start %Ld end %Ld)\n", + resp->status, (int)resp->lock.svid, fl->fl_type, (long long)fl->fl_start, (long long)fl->fl_end); } @@ -226,7 +179,7 @@ nlm4_encode_testres(u32 *p, struct nlm_res *resp) * First, the server side XDR functions */ int -nlm4svc_decode_testargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) +nlm4svc_decode_testargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) { u32 exclusive; @@ -243,7 +196,7 @@ nlm4svc_decode_testargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) } int -nlm4svc_encode_testres(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) +nlm4svc_encode_testres(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) { if (!(p = nlm4_encode_testres(p, resp))) return 0; @@ -251,7 +204,7 @@ nlm4svc_encode_testres(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) } int -nlm4svc_decode_lockargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) +nlm4svc_decode_lockargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) { u32 exclusive; @@ -271,7 +224,7 @@ nlm4svc_decode_lockargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) } int -nlm4svc_decode_cancargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) +nlm4svc_decode_cancargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) { u32 exclusive; @@ -287,7 +240,7 @@ nlm4svc_decode_cancargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) } int -nlm4svc_decode_unlockargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) +nlm4svc_decode_unlockargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) { if (!(p = nlm4_decode_cookie(p, &argp->cookie)) || !(p = nlm4_decode_lock(p, &argp->lock))) @@ -297,13 +250,14 @@ nlm4svc_decode_unlockargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) } int -nlm4svc_decode_shareargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) +nlm4svc_decode_shareargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) { struct nlm_lock *lock = &argp->lock; memset(lock, 0, sizeof(*lock)); locks_init_lock(&lock->fl); - lock->fl.fl_pid = ~(u32) 0; + lock->svid = ~(u32) 0; + lock->fl.fl_pid = (pid_t)lock->svid; if (!(p = nlm4_decode_cookie(p, &argp->cookie)) || !(p = xdr_decode_string_inplace(p, &lock->caller, @@ -317,7 +271,7 @@ nlm4svc_decode_shareargs(struct svc_rqst *rqstp, u32 *p, nlm_args *argp) } int -nlm4svc_encode_shareres(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) +nlm4svc_encode_shareres(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) { if (!(p = nlm4_encode_cookie(p, &resp->cookie))) return 0; @@ -327,7 +281,7 @@ nlm4svc_encode_shareres(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) } int -nlm4svc_encode_res(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) +nlm4svc_encode_res(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) { if (!(p = nlm4_encode_cookie(p, &resp->cookie))) return 0; @@ -336,7 +290,7 @@ nlm4svc_encode_res(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) } int -nlm4svc_decode_notify(struct svc_rqst *rqstp, u32 *p, struct nlm_args *argp) +nlm4svc_decode_notify(struct svc_rqst *rqstp, __be32 *p, struct nlm_args *argp) { struct nlm_lock *lock = &argp->lock; @@ -348,235 +302,33 @@ nlm4svc_decode_notify(struct svc_rqst *rqstp, u32 *p, struct nlm_args *argp) } int -nlm4svc_decode_reboot(struct svc_rqst *rqstp, u32 *p, struct nlm_reboot *argp) +nlm4svc_decode_reboot(struct svc_rqst *rqstp, __be32 *p, struct nlm_reboot *argp) { if (!(p = xdr_decode_string_inplace(p, &argp->mon, &argp->len, SM_MAXSTRLEN))) return 0; argp->state = ntohl(*p++); - /* Preserve the address in network byte order */ - argp->addr = *p++; - argp->vers = *p++; - argp->proto = *p++; + memcpy(&argp->priv.data, p, sizeof(argp->priv.data)); + p += XDR_QUADLEN(SM_PRIV_SIZE); return xdr_argsize_check(rqstp, p); } int -nlm4svc_decode_res(struct svc_rqst *rqstp, u32 *p, struct nlm_res *resp) +nlm4svc_decode_res(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) { if (!(p = nlm4_decode_cookie(p, &resp->cookie))) return 0; - resp->status = ntohl(*p++); + resp->status = *p++; return xdr_argsize_check(rqstp, p); } int -nlm4svc_decode_void(struct svc_rqst *rqstp, u32 *p, void *dummy) +nlm4svc_decode_void(struct svc_rqst *rqstp, __be32 *p, void *dummy) { return xdr_argsize_check(rqstp, p); } int -nlm4svc_encode_void(struct svc_rqst *rqstp, u32 *p, void *dummy) +nlm4svc_encode_void(struct svc_rqst *rqstp, __be32 *p, void *dummy) { return xdr_ressize_check(rqstp, p); } - -/* - * Now, the client side XDR functions - */ -#ifdef NLMCLNT_SUPPORT_SHARES -static int -nlm4clt_decode_void(struct rpc_rqst *req, u32 *p, void *ptr) -{ - return 0; -} -#endif - -static int -nlm4clt_encode_testargs(struct rpc_rqst *req, u32 *p, nlm_args *argp) -{ - struct nlm_lock *lock = &argp->lock; - - if (!(p = nlm4_encode_cookie(p, &argp->cookie))) - return -EIO; - *p++ = (lock->fl.fl_type == F_WRLCK)? xdr_one : xdr_zero; - if (!(p = nlm4_encode_lock(p, lock))) - return -EIO; - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlm4clt_decode_testres(struct rpc_rqst *req, u32 *p, struct nlm_res *resp) -{ - if (!(p = nlm4_decode_cookie(p, &resp->cookie))) - return -EIO; - resp->status = ntohl(*p++); - if (resp->status == NLM_LCK_DENIED) { - struct file_lock *fl = &resp->lock.fl; - u32 excl; - s64 start, end, len; - - memset(&resp->lock, 0, sizeof(resp->lock)); - locks_init_lock(fl); - excl = ntohl(*p++); - fl->fl_pid = ntohl(*p++); - if (!(p = nlm4_decode_oh(p, &resp->lock.oh))) - return -EIO; - - fl->fl_flags = FL_POSIX; - fl->fl_type = excl? F_WRLCK : F_RDLCK; - p = xdr_decode_hyper(p, &start); - p = xdr_decode_hyper(p, &len); - end = start + len - 1; - - fl->fl_start = s64_to_loff_t(start); - if (len == 0 || end < 0) - fl->fl_end = OFFSET_MAX; - else - fl->fl_end = s64_to_loff_t(end); - } - return 0; -} - - -static int -nlm4clt_encode_lockargs(struct rpc_rqst *req, u32 *p, nlm_args *argp) -{ - struct nlm_lock *lock = &argp->lock; - - if (!(p = nlm4_encode_cookie(p, &argp->cookie))) - return -EIO; - *p++ = argp->block? xdr_one : xdr_zero; - *p++ = (lock->fl.fl_type == F_WRLCK)? xdr_one : xdr_zero; - if (!(p = nlm4_encode_lock(p, lock))) - return -EIO; - *p++ = argp->reclaim? xdr_one : xdr_zero; - *p++ = htonl(argp->state); - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlm4clt_encode_cancargs(struct rpc_rqst *req, u32 *p, nlm_args *argp) -{ - struct nlm_lock *lock = &argp->lock; - - if (!(p = nlm4_encode_cookie(p, &argp->cookie))) - return -EIO; - *p++ = argp->block? xdr_one : xdr_zero; - *p++ = (lock->fl.fl_type == F_WRLCK)? xdr_one : xdr_zero; - if (!(p = nlm4_encode_lock(p, lock))) - return -EIO; - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlm4clt_encode_unlockargs(struct rpc_rqst *req, u32 *p, nlm_args *argp) -{ - struct nlm_lock *lock = &argp->lock; - - if (!(p = nlm4_encode_cookie(p, &argp->cookie))) - return -EIO; - if (!(p = nlm4_encode_lock(p, lock))) - return -EIO; - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlm4clt_encode_res(struct rpc_rqst *req, u32 *p, struct nlm_res *resp) -{ - if (!(p = nlm4_encode_cookie(p, &resp->cookie))) - return -EIO; - *p++ = resp->status; - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlm4clt_encode_testres(struct rpc_rqst *req, u32 *p, struct nlm_res *resp) -{ - if (!(p = nlm4_encode_testres(p, resp))) - return -EIO; - req->rq_slen = xdr_adjust_iovec(req->rq_svec, p); - return 0; -} - -static int -nlm4clt_decode_res(struct rpc_rqst *req, u32 *p, struct nlm_res *resp) -{ - if (!(p = nlm4_decode_cookie(p, &resp->cookie))) - return -EIO; - resp->status = ntohl(*p++); - return 0; -} - -/* - * Buffer requirements for NLM - */ -#define NLM4_void_sz 0 -#define NLM4_cookie_sz 1+XDR_QUADLEN(NLM_MAXCOOKIELEN) -#define NLM4_caller_sz 1+XDR_QUADLEN(NLM_MAXSTRLEN) -#define NLM4_netobj_sz 1+XDR_QUADLEN(XDR_MAX_NETOBJ) -/* #define NLM4_owner_sz 1+XDR_QUADLEN(NLM4_MAXOWNER) */ -#define NLM4_fhandle_sz 1+XDR_QUADLEN(NFS3_FHSIZE) -#define NLM4_lock_sz 5+NLM4_caller_sz+NLM4_netobj_sz+NLM4_fhandle_sz -#define NLM4_holder_sz 6+NLM4_netobj_sz - -#define NLM4_testargs_sz NLM4_cookie_sz+1+NLM4_lock_sz -#define NLM4_lockargs_sz NLM4_cookie_sz+4+NLM4_lock_sz -#define NLM4_cancargs_sz NLM4_cookie_sz+2+NLM4_lock_sz -#define NLM4_unlockargs_sz NLM4_cookie_sz+NLM4_lock_sz - -#define NLM4_testres_sz NLM4_cookie_sz+1+NLM4_holder_sz -#define NLM4_res_sz NLM4_cookie_sz+1 -#define NLM4_norep_sz 0 - -#ifndef MAX -# define MAX(a,b) (((a) > (b))? (a) : (b)) -#endif - -/* - * For NLM, a void procedure really returns nothing - */ -#define nlm4clt_decode_norep NULL - -#define PROC(proc, argtype, restype) \ -[NLMPROC_##proc] = { \ - .p_proc = NLMPROC_##proc, \ - .p_encode = (kxdrproc_t) nlm4clt_encode_##argtype, \ - .p_decode = (kxdrproc_t) nlm4clt_decode_##restype, \ - .p_bufsiz = MAX(NLM4_##argtype##_sz, NLM4_##restype##_sz) << 2 \ - } - -static struct rpc_procinfo nlm4_procedures[] = { - PROC(TEST, testargs, testres), - PROC(LOCK, lockargs, res), - PROC(CANCEL, cancargs, res), - PROC(UNLOCK, unlockargs, res), - PROC(GRANTED, testargs, res), - PROC(TEST_MSG, testargs, norep), - PROC(LOCK_MSG, lockargs, norep), - PROC(CANCEL_MSG, cancargs, norep), - PROC(UNLOCK_MSG, unlockargs, norep), - PROC(GRANTED_MSG, testargs, norep), - PROC(TEST_RES, testres, norep), - PROC(LOCK_RES, res, norep), - PROC(CANCEL_RES, res, norep), - PROC(UNLOCK_RES, res, norep), - PROC(GRANTED_RES, res, norep), -#ifdef NLMCLNT_SUPPORT_SHARES - PROC(SHARE, shareargs, shareres), - PROC(UNSHARE, shareargs, shareres), - PROC(NM_LOCK, lockargs, res), - PROC(FREE_ALL, notify, void), -#endif -}; - -struct rpc_version nlm_version4 = { - .number = 4, - .nrprocs = 24, - .procs = nlm4_procedures, -}; |
