diff options
author | Kim Kibum <kb0929.kim@samsung.com> | 2012-04-29 16:59:19 +0900 |
---|---|---|
committer | Kim Kibum <kb0929.kim@samsung.com> | 2012-04-29 16:59:19 +0900 |
commit | c1775d1a93a77a57380a4ce87ac3a8f807c944b2 (patch) | |
tree | e1f233f2af38ee247a677082198dd3a69a12a5a1 /fs/lockd | |
parent | 2c2dcd5ffef2e97176e6a55e45512177e55e6fb9 (diff) | |
download | linux-2.6.36-master.tar.gz linux-2.6.36-master.tar.bz2 linux-2.6.36-master.zip |
Diffstat (limited to 'fs/lockd')
-rw-r--r-- | fs/lockd/Makefile | 10 | ||||
-rw-r--r-- | fs/lockd/clntlock.c | 275 | ||||
-rw-r--r-- | fs/lockd/clntproc.c | 848 | ||||
-rw-r--r-- | fs/lockd/grace.c | 59 | ||||
-rw-r--r-- | fs/lockd/host.c | 569 | ||||
-rw-r--r-- | fs/lockd/mon.c | 580 | ||||
-rw-r--r-- | fs/lockd/svc.c | 579 | ||||
-rw-r--r-- | fs/lockd/svc4proc.c | 506 | ||||
-rw-r--r-- | fs/lockd/svclock.c | 924 | ||||
-rw-r--r-- | fs/lockd/svcproc.c | 539 | ||||
-rw-r--r-- | fs/lockd/svcshare.c | 106 | ||||
-rw-r--r-- | fs/lockd/svcsubs.c | 439 | ||||
-rw-r--r-- | fs/lockd/xdr.c | 630 | ||||
-rw-r--r-- | fs/lockd/xdr4.c | 589 |
14 files changed, 6653 insertions, 0 deletions
diff --git a/fs/lockd/Makefile b/fs/lockd/Makefile new file mode 100644 index 00000000..97f6073a --- /dev/null +++ b/fs/lockd/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for the linux lock manager stuff +# + +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 grace.o +lockd-objs-$(CONFIG_LOCKD_V4) += xdr4.o svc4proc.o +lockd-objs := $(lockd-objs-y) diff --git a/fs/lockd/clntlock.c b/fs/lockd/clntlock.c new file mode 100644 index 00000000..64fd427c --- /dev/null +++ b/fs/lockd/clntlock.c @@ -0,0 +1,275 @@ +/* + * linux/fs/lockd/clntlock.c + * + * Lock handling for the client side NLM implementation + * + * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> + */ + +#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/svc.h> +#include <linux/lockd/lockd.h> +#include <linux/smp_lock.h> +#include <linux/kthread.h> + +#define NLMDBG_FACILITY NLMDBG_CLIENT + +/* + * Local function prototypes + */ +static int reclaimer(void *ptr); + +/* + * The following functions handle blocking and granting from the + * client perspective. + */ + +/* + * This is the representation of a blocked client lock. + */ +struct nlm_wait { + struct list_head b_list; /* linked list */ + wait_queue_head_t b_wait; /* where to wait on */ + struct nlm_host * b_host; + struct file_lock * b_lock; /* local file lock */ + unsigned short b_reclaim; /* got to reclaim lock */ + __be32 b_status; /* grant callback status */ +}; + +static LIST_HEAD(nlm_blocked); + +/** + * 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(); + 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); + if (host == NULL) { + lockd_down(); + return ERR_PTR(-ENOLCK); + } + + return host; +} +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) +{ + nlm_release_host(host); + lockd_down(); +} +EXPORT_SYMBOL_GPL(nlmclnt_done); + +/* + * Queue up a lock for blocking so that the GRANTED request can see it + */ +struct nlm_wait *nlmclnt_prepare_block(struct nlm_host *host, struct file_lock *fl) +{ + struct nlm_wait *block; + + block = kmalloc(sizeof(*block), GFP_KERNEL); + if (block != NULL) { + 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); + } + return block; +} + +void nlmclnt_finish_block(struct nlm_wait *block) +{ + if (block == NULL) + return; + list_del(&block->b_list); + kfree(block); +} + +/* + * Block on a lock + */ +int nlmclnt_block(struct nlm_wait *block, struct nlm_rqst *req, long timeout) +{ + long ret; + + /* A borken server might ask us to block even if we didn't + * request it. Just say no! + */ + if (block == NULL) + return -EAGAIN; + + /* Go to sleep waiting for GRANT callback. Some servers seem + * to lose callbacks, however, so we're going to poll from + * time to time just to make sure. + * + * For now, the retry frequency is pretty high; normally + * a 1 minute timeout would do. See the comment before + * nlmclnt_lock for an explanation. + */ + ret = wait_event_interruptible_timeout(block->b_wait, + block->b_status != nlm_lck_blocked, + timeout); + if (ret < 0) + return -ERESTARTSYS; + req->a_res.status = block->b_status; + return 0; +} + +/* + * The server lockd has called us back to tell us the lock was granted + */ +__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; + __be32 res = nlm_lck_denied; + + /* + * Look up blocked request based on arguments. + * Warning: must not use cookie to match it! + */ + list_for_each_entry(block, &nlm_blocked, b_list) { + struct file_lock *fl_blocked = block->b_lock; + + if (fl_blocked->fl_start != fl->fl_start) + continue; + if (fl_blocked->fl_end != fl->fl_end) + continue; + /* + * 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 (!rpc_cmp_addr(nlm_addr(block->b_host), addr)) + continue; + if (nfs_compare_fh(NFS_FH(fl_blocked->fl_file->f_path.dentry->d_inode) ,fh) != 0) + continue; + /* 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; + } + return res; +} + +/* + * The following procedures deal with the recovery of locks after a + * server crash. + */ + +/* + * Reclaim all locks on server host. We do this by spawning a separate + * reclaimer thread. + */ +void +nlmclnt_recovery(struct nlm_host *host) +{ + struct task_struct *task; + + if (!host->h_reclaiming++) { + nlm_get_host(host); + 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)); + } +} + +static int +reclaimer(void *ptr) +{ + struct nlm_host *host = (struct nlm_host *) ptr; + struct nlm_wait *block; + struct file_lock *fl, *next; + u32 nsmstate; + + allow_signal(SIGKILL); + + down_write(&host->h_rwsem); + + /* This one ensures that our parent doesn't terminate while the + * reclaim is in progress */ + lock_kernel(); + lockd_up(); /* note: this cannot fail as lockd is already running */ + + dprintk("lockd: reclaiming locks for host %s\n", host->h_name); + +restart: + nsmstate = host->h_nsmstate; + + /* 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); + + /* 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()) + continue; + if (nlmclnt_reclaim(host, fl) != 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 */ + list_for_each_entry(block, &nlm_blocked, b_list) { + if (block->b_host == host) { + block->b_status = nlm_lck_denied_grace_period; + wake_up(&block->b_wait); + } + } + + /* Release host handle after use */ + nlm_release_host(host); + lockd_down(); + unlock_kernel(); + return 0; +} diff --git a/fs/lockd/clntproc.c b/fs/lockd/clntproc.c new file mode 100644 index 00000000..7932c399 --- /dev/null +++ b/fs/lockd/clntproc.c @@ -0,0 +1,848 @@ +/* + * linux/fs/lockd/clntproc.c + * + * RPC procedures for the client side NLM implementation + * + * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> + */ + +#include <linux/module.h> +#include <linux/smp_lock.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/freezer.h> +#include <linux/sunrpc/clnt.h> +#include <linux/sunrpc/svc.h> +#include <linux/lockd/lockd.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(__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; + +/* + * Cookie counter for NLM requests + */ +static atomic_t nlm_cookie = ATOMIC_INIT(0x1234); + +void nlmclnt_next_cookie(struct nlm_cookie *c) +{ + u32 cookie = atomic_inc_return(&nlm_cookie); + + memcpy(c->data, &cookie, 4); + c->len=4; +} + +static struct nlm_lockowner *nlm_get_lockowner(struct nlm_lockowner *lockowner) +{ + atomic_inc(&lockowner->count); + return lockowner; +} + +static void nlm_put_lockowner(struct nlm_lockowner *lockowner) +{ + if (!atomic_dec_and_lock(&lockowner->count, &lockowner->host->h_lock)) + return; + list_del(&lockowner->list); + spin_unlock(&lockowner->host->h_lock); + nlm_release_host(lockowner->host); + kfree(lockowner); +} + +static inline int nlm_pidbusy(struct nlm_host *host, uint32_t pid) +{ + struct nlm_lockowner *lockowner; + list_for_each_entry(lockowner, &host->h_lockowners, list) { + if (lockowner->pid == pid) + return -EBUSY; + } + return 0; +} + +static inline uint32_t __nlm_alloc_pid(struct nlm_host *host) +{ + uint32_t res; + do { + res = host->h_pidcount++; + } while (nlm_pidbusy(host, res) < 0); + return res; +} + +static struct nlm_lockowner *__nlm_find_lockowner(struct nlm_host *host, fl_owner_t owner) +{ + struct nlm_lockowner *lockowner; + list_for_each_entry(lockowner, &host->h_lockowners, list) { + if (lockowner->owner != owner) + continue; + return nlm_get_lockowner(lockowner); + } + return NULL; +} + +static struct nlm_lockowner *nlm_find_lockowner(struct nlm_host *host, fl_owner_t owner) +{ + struct nlm_lockowner *res, *new = NULL; + + spin_lock(&host->h_lock); + res = __nlm_find_lockowner(host, owner); + if (res == NULL) { + spin_unlock(&host->h_lock); + new = kmalloc(sizeof(*new), GFP_KERNEL); + spin_lock(&host->h_lock); + res = __nlm_find_lockowner(host, owner); + if (res == NULL && new != NULL) { + res = new; + atomic_set(&new->count, 1); + new->owner = owner; + new->pid = __nlm_alloc_pid(host); + new->host = nlm_get_host(host); + list_add(&new->list, &host->h_lockowners); + new = NULL; + } + } + spin_unlock(&host->h_lock); + kfree(new); + return res; +} + +/* + * Initialize arguments for TEST/LOCK/UNLOCK/CANCEL calls + */ +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; + + nlmclnt_next_cookie(&argp->cookie); + memcpy(&lock->fh, NFS_FH(fl->fl_file->f_path.dentry->d_inode), sizeof(struct nfs_fh)); + lock->caller = utsname()->nodename; + lock->oh.data = req->a_owner; + lock->oh.len = snprintf(req->a_owner, sizeof(req->a_owner), "%u@%s", + (unsigned int)fl->fl_u.nfs_fl.owner->pid, + utsname()->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) +{ + BUG_ON(req->a_args.lock.fl.fl_ops != NULL); +} + +/** + * 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_proc(struct nlm_host *host, int cmd, struct file_lock *fl) +{ + struct nlm_rqst *call; + int status; + + nlm_get_host(host); + call = nlm_alloc_call(host); + if (call == NULL) + return -ENOMEM; + + nlmclnt_locks_init_private(fl, host); + /* Set up the argument struct */ + nlmclnt_setlockargs(call, fl); + + lock_kernel(); + if (IS_SETLK(cmd) || IS_SETLKW(cmd)) { + if (fl->fl_type != F_UNLCK) { + call->a_args.block = IS_SETLKW(cmd) ? 1 : 0; + status = nlmclnt_lock(call, fl); + } else + status = nlmclnt_unlock(call, fl); + } else if (IS_GETLK(cmd)) + status = nlmclnt_test(call, fl); + else + status = -EINVAL; + + fl->fl_ops->fl_release_private(fl); + fl->fl_ops = NULL; + unlock_kernel(); + + dprintk("lockd: clnt proc returns %d\n", status); + return status; +} +EXPORT_SYMBOL_GPL(nlmclnt_proc); + +/* + * Allocate an NLM RPC call struct + * + * Note: the caller must hold a reference to host. In case of failure, + * this reference will be released. + */ +struct nlm_rqst *nlm_alloc_call(struct nlm_host *host) +{ + struct nlm_rqst *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 = host; + return call; + } + if (signalled()) + break; + printk("nlm_alloc_call: failed, waiting for memory\n"); + schedule_timeout_interruptible(5*HZ); + } + nlm_release_host(host); + return NULL; +} + +void nlm_release_call(struct nlm_rqst *call) +{ + if (!atomic_dec_and_test(&call->a_count)) + return; + nlm_release_host(call->a_host); + nlmclnt_release_lockargs(call); + kfree(call); +} + +static void nlmclnt_rpc_release(void *data) +{ + lock_kernel(); + nlm_release_call(data); + unlock_kernel(); +} + +static int nlm_wait_on_grace(wait_queue_head_t *queue) +{ + DEFINE_WAIT(wait); + int status = -EINTR; + + prepare_to_wait(queue, &wait, TASK_INTERRUPTIBLE); + if (!signalled ()) { + schedule_timeout(NLMCLNT_GRACE_WAIT); + try_to_freeze(); + if (!signalled ()) + status = 0; + } + finish_wait(queue, &wait); + return status; +} + +/* + * Generic NLM call + */ +static int +nlmclnt_call(struct rpc_cred *cred, struct nlm_rqst *req, u32 proc) +{ + 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_cred = cred, + }; + int status; + + dprintk("lockd: call procedure %d on %s\n", + (int)proc, host->h_name); + + do { + if (host->h_reclaiming && !argp->reclaim) + goto in_grace_period; + + /* 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]; + + /* Perform the RPC call. If an error occurs, try again */ + if ((status = rpc_call_sync(clnt, &msg, 0)) < 0) { + dprintk("lockd: rpc_call returned error %d\n", -status); + switch (status) { + case -EPROTONOSUPPORT: + status = -EINVAL; + break; + case -ECONNREFUSED: + case -ETIMEDOUT: + case -ENOTCONN: + nlm_rebind_host(host); + status = -EAGAIN; + break; + case -ERESTARTSYS: + return signalled () ? -EINTR : status; + default: + break; + } + break; + } else + if (resp->status == nlm_lck_denied_grace_period) { + dprintk("lockd: server in grace period\n"); + if (argp->reclaim) { + printk(KERN_WARNING + "lockd: spurious grace period reject?!\n"); + return -ENOLCK; + } + } else { + if (!argp->reclaim) { + /* We appear to be out of the grace period */ + wake_up_all(&host->h_gracewait); + } + dprintk("lockd: server returns status %d\n", resp->status); + return 0; /* Okay, call complete */ + } + +in_grace_period: + /* + * The server has rebooted and appears to be in the grace + * period during which locks are only allowed to be + * reclaimed. + * We can only back off and try again later. + */ + status = nlm_wait_on_grace(&host->h_gracewait); + } while (status == 0); + + return status; +} + +/* + * Generic NLM call, async version. + */ +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_task_setup task_setup_data = { + .rpc_message = msg, + .callback_ops = tk_ops, + .callback_data = req, + .flags = RPC_TASK_ASYNC, + }; + + dprintk("lockd: call procedure %d on %s (async)\n", + (int)proc, host->h_name); + + /* If we have no RPC client yet, create one. */ + 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 */ + return rpc_run_task(&task_setup_data); +out_err: + tk_ops->rpc_release(req); + return ERR_PTR(-ENOLCK); +} + +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; +} + +/* + * NLM asynchronous call. + */ +int nlm_async_call(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, + }; + return nlm_do_async_call(req, proc, &msg, tk_ops); +} + +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); +} + +/* + * 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; +} + +/* + * TEST for the presence of a conflicting lock + */ +static int +nlmclnt_test(struct nlm_rqst *req, struct file_lock *fl) +{ + int status; + + status = nlmclnt_call(nfs_file_cred(fl->fl_file), req, NLMPROC_TEST); + if (status < 0) + goto out; + + 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); + } +out: + nlm_release_call(req); + return status; +} + +static void nlmclnt_locks_copy_lock(struct file_lock *new, struct file_lock *fl) +{ + 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); +} + +static void nlmclnt_locks_release_private(struct file_lock *fl) +{ + list_del(&fl->fl_u.nfs_fl.list); + nlm_put_lockowner(fl->fl_u.nfs_fl.owner); +} + +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.owner = nlm_find_lockowner(host, fl->fl_owner); + INIT_LIST_HEAD(&fl->fl_u.nfs_fl.list); + fl->fl_ops = &nlmclnt_lock_ops; +} + +static int do_vfs_lock(struct file_lock *fl) +{ + int res = 0; + switch (fl->fl_flags & (FL_POSIX|FL_FLOCK)) { + case FL_POSIX: + res = posix_lock_file_wait(fl->fl_file, fl); + break; + case FL_FLOCK: + res = flock_lock_file_wait(fl->fl_file, fl); + break; + default: + BUG(); + } + return res; +} + +/* + * LOCK: Try to create a lock + * + * Programmer Harassment Alert + * + * When given a blocking lock request in a sync RPC call, the HPUX lockd + * will faithfully return LCK_BLOCKED but never cares to notify us when + * the lock could be granted. This way, our local process could hang + * around forever waiting for the callback. + * + * Solution A: Implement busy-waiting + * Solution B: Use the async version of the call (NLM_LOCK_{MSG,RES}) + * + * For now I am implementing solution A, because I hate the idea of + * re-implementing lockd for a third time in two months. The async + * calls shouldn't be too hard to do, however. + * + * This is one of the lovely things about standards in the NFS area: + * they're so soft and squishy you can't really blame HP for doing this. + */ +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; + struct nlm_wait *block = NULL; + unsigned char fl_flags = fl->fl_flags; + unsigned char fl_type; + int status = -ENOLCK; + + if (nsm_monitor(host) < 0) + goto out; + req->a_args.state = nsm_local_state; + + 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(;;) { + /* Reboot protection */ + fl->fl_u.nfs_fl.state = host->h_state; + status = nlmclnt_call(cred, req, NLMPROC_LOCK); + if (status < 0) + break; + /* Did a reclaimer thread notify us of a server reboot? */ + if (resp->status == nlm_lck_denied_grace_period) + continue; + 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 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; + 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; + } + 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(block); +out: + nlm_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; +} + +/* + * RECLAIM: Try to reclaim a lock + */ +int +nlmclnt_reclaim(struct nlm_host *host, struct file_lock *fl) +{ + 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; + + 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, ntohl(req->a_res.status)); + + /* + * FIXME: This is a serious failure. We can + * + * a. Ignore the problem + * b. Send the owning process some signal (Linux doesn't have + * SIGLOST, though...) + * c. Retry the operation + * + * Until someone comes up with a simple implementation + * for b or c, I'll choose option a. + */ + + return -ENOLCK; +} + +/* + * UNLOCK: remove an existing lock + */ +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; + unsigned char fl_flags = fl->fl_flags; + + /* + * 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; + } + + atomic_inc(&req->a_count); + status = nlmclnt_async_call(nfs_file_cred(fl->fl_file), req, + NLMPROC_UNLOCK, &nlmclnt_unlock_ops); + if (status < 0) + goto out; + + if (resp->status == nlm_granted) + goto out; + + if (resp->status != nlm_lck_denied_nolocks) + printk("lockd: unexpected unlock status: %d\n", resp->status); + /* What to do now? I'm out of my depth... */ + status = -ENOLCK; +out: + nlm_release_call(req); + return status; +} + +static void nlmclnt_unlock_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; + + if (task->tk_status < 0) { + dprintk("lockd: unlock failed (err = %d)\n", -task->tk_status); + goto retry_rebind; + } + if (status == NLM_LCK_DENIED_GRACE_PERIOD) { + rpc_delay(task, NLMCLNT_GRACE_WAIT); + goto retry_unlock; + } + if (status != NLM_LCK_GRANTED) + printk(KERN_WARNING "lockd: unexpected unlock status: %d\n", status); +die: + return; + retry_rebind: + lock_kernel(); + nlm_rebind_host(req->a_host); + unlock_kernel(); + retry_unlock: + rpc_restart_call(task); +} + +static const struct rpc_call_ops nlmclnt_unlock_ops = { + .rpc_call_done = nlmclnt_unlock_callback, + .rpc_release = nlmclnt_rpc_release, +}; + +/* + * Cancel a blocked lock request. + * We always use an async RPC call for this in order not to hang a + * process that has been Ctrl-C'ed. + */ +static int nlmclnt_cancel(struct nlm_host *host, int block, struct file_lock *fl) +{ + struct nlm_rqst *req; + int status; + + dprintk("lockd: blocking lock attempt was interrupted by a signal.\n" + " Attempting to cancel lock.\n"); + + req = nlm_alloc_call(nlm_get_host(host)); + if (!req) + return -ENOMEM; + req->a_flags = RPC_TASK_ASYNC; + + nlmclnt_setlockargs(req, fl); + req->a_args.block = block; + + 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; + nlm_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; + + if (task->tk_status < 0) { + dprintk("lockd: CANCEL call error %d, retrying.\n", + task->tk_status); + goto retry_cancel; + } + + dprintk("lockd: cancel status %u (task %u)\n", + status, task->tk_pid); + + 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: + dprintk("lockd: CANCEL failed (server has no locks)\n"); + goto retry_cancel; + default: + printk(KERN_NOTICE "lockd: weird return %d for CANCEL call\n", + status); + } + +die: + return; + +retry_cancel: + /* Don't ever retry more than 3 times */ + if (req->a_retries++ >= NLMCLNT_MAX_RETRIES) + goto die; + lock_kernel(); + nlm_rebind_host(req->a_host); + unlock_kernel(); + rpc_restart_call(task); + rpc_delay(task, 30 * HZ); +} + +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(__be32 status) +{ + switch(ntohl(status)) { + case NLM_LCK_GRANTED: + return 0; + case NLM_LCK_DENIED: + return -EAGAIN; + case NLM_LCK_DENIED_NOLOCKS: + case NLM_LCK_DENIED_GRACE_PERIOD: + return -ENOLCK; + case NLM_LCK_BLOCKED: + printk(KERN_NOTICE "lockd: unexpected status NLM_BLOCKED\n"); + return -ENOLCK; +#ifdef CONFIG_LOCKD_V4 + case NLM_DEADLCK: + return -EDEADLK; + case NLM_ROFS: + return -EROFS; + case NLM_STALE_FH: + return -ESTALE; + case NLM_FBIG: + return -EOVERFLOW; + case NLM_FAILED: + return -ENOLCK; +#endif + } + printk(KERN_NOTICE "lockd: unexpected server status %d\n", status); + return -ENOLCK; +} diff --git a/fs/lockd/grace.c b/fs/lockd/grace.c new file mode 100644 index 00000000..183cc1f0 --- /dev/null +++ b/fs/lockd/grace.c @@ -0,0 +1,59 @@ +/* + * Common code for control of lockd and nfsv4 grace periods. + */ + +#include <linux/module.h> +#include <linux/lockd/bind.h> + +static LIST_HEAD(grace_list); +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 lock_manager *lm) +{ + spin_lock(&grace_lock); + list_add(&lm->list, &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(void) +{ + return !list_empty(&grace_list); +} +EXPORT_SYMBOL_GPL(locks_in_grace); diff --git a/fs/lockd/host.c b/fs/lockd/host.c new file mode 100644 index 00000000..bb464d12 --- /dev/null +++ b/fs/lockd/host.c @@ -0,0 +1,569 @@ +/* + * linux/fs/lockd/host.c + * + * Management for NLM peer hosts. The nlm_host struct is shared + * between client and server implementation. The only reason to + * do so is to reduce code bloat. + * + * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> + */ + +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/in.h> +#include <linux/in6.h> +#include <linux/sunrpc/clnt.h> +#include <linux/sunrpc/svc.h> +#include <linux/lockd/lockd.h> +#include <linux/mutex.h> + +#include <net/ipv6.h> + +#define NLMDBG_FACILITY NLMDBG_HOSTCACHE +#define NLM_HOST_NRHASH 32 +#define NLM_HOST_REBIND (60 * HZ) +#define NLM_HOST_EXPIRE (300 * HZ) +#define NLM_HOST_COLLECT (120 * HZ) + +static struct hlist_head nlm_hosts[NLM_HOST_NRHASH]; +static unsigned long next_gc; +static int nrhosts; +static DEFINE_MUTEX(nlm_host_mutex); + +static void nlm_gc_hosts(void); + +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 struct sockaddr *src_sap; /* our address (optional) */ + const size_t src_len; /* it's length */ + const int noresvport; /* use non-priv port */ +}; + +/* + * 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 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); +} + +/* + * Common host lookup routine for server & client + */ +static struct nlm_host *nlm_lookup_host(struct nlm_lookup_host_info *ni) +{ + struct hlist_head *chain; + struct hlist_node *pos; + struct nlm_host *host; + struct nsm_handle *nsm = NULL; + + mutex_lock(&nlm_host_mutex); + + if (time_after_eq(jiffies, next_gc)) + nlm_gc_hosts(); + + /* We may keep several nlm_host objects for a peer, because each + * nlm_host is identified by + * (address, protocol, version, server/client) + * We could probably simplify this a little by putting all those + * different NLM rpc_clients into one single nlm_host object. + * This would allow us to have one nlm_host per address. + */ + chain = &nlm_hosts[nlm_hash_address(ni->sap)]; + hlist_for_each_entry(host, pos, chain, h_hash) { + if (!rpc_cmp_addr(nlm_addr(host), ni->sap)) + continue; + + /* See if we have an NSM handle for this client */ + if (!nsm) + nsm = host->h_nsmhandle; + + if (host->h_proto != ni->protocol) + continue; + if (host->h_version != ni->version) + continue; + if (host->h_server != ni->server) + continue; + if (ni->server && + !rpc_cmp_addr(nlm_srcaddr(host), ni->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: nlm_lookup_host found host %s (%s)\n", + host->h_name, host->h_addrbuf); + goto out; + } + + /* + * The host wasn't in our hash table. If we don't + * have an NSM handle for it yet, create one. + */ + if (nsm) + atomic_inc(&nsm->sm_count); + else { + host = NULL; + nsm = nsm_get_handle(ni->sap, ni->salen, + ni->hostname, ni->hostname_len); + if (!nsm) { + dprintk("lockd: nlm_lookup_host failed; " + "no nsm handle\n"); + goto out; + } + } + + host = kzalloc(sizeof(*host), GFP_KERNEL); + if (!host) { + nsm_release(nsm); + dprintk("lockd: nlm_lookup_host failed; no memory\n"); + goto out; + } + host->h_name = nsm->sm_name; + host->h_addrbuf = nsm->sm_addrbuf; + memcpy(nlm_addr(host), ni->sap, ni->salen); + host->h_addrlen = ni->salen; + rpc_set_port(nlm_addr(host), 0); + memcpy(nlm_srcaddr(host), ni->src_sap, ni->src_len); + host->h_version = ni->version; + host->h_proto = ni->protocol; + host->h_rpcclnt = NULL; + mutex_init(&host->h_mutex); + 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); + init_rwsem(&host->h_rwsem); + host->h_state = 0; /* pseudo NSM state */ + host->h_nsmstate = 0; /* real NSM state */ + host->h_nsmhandle = nsm; + host->h_server = ni->server; + host->h_noresvport = ni->noresvport; + hlist_add_head(&host->h_hash, chain); + INIT_LIST_HEAD(&host->h_lockowners); + spin_lock_init(&host->h_lock); + INIT_LIST_HEAD(&host->h_granted); + INIT_LIST_HEAD(&host->h_reclaim); + + nrhosts++; + + dprintk("lockd: nlm_lookup_host created host %s\n", + host->h_name); + +out: + mutex_unlock(&nlm_host_mutex); + return host; +} + +/* + * Destroy a host + */ +static void +nlm_destroy_host(struct nlm_host *host) +{ + struct rpc_clnt *clnt; + + BUG_ON(!list_empty(&host->h_lockowners)); + BUG_ON(atomic_read(&host->h_count)); + + nsm_unmonitor(host); + nsm_release(host->h_nsmhandle); + + clnt = host->h_rpcclnt; + if (clnt != NULL) + rpc_shutdown_client(clnt); + kfree(host); +} + +/** + * 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 *nlmclnt_lookup_host(const struct sockaddr *sap, + const size_t salen, + const unsigned short protocol, + const u32 version, + const char *hostname, + int noresvport) +{ + const struct sockaddr source = { + .sa_family = AF_UNSPEC, + }; + struct nlm_lookup_host_info ni = { + .server = 0, + .sap = sap, + .salen = salen, + .protocol = protocol, + .version = version, + .hostname = hostname, + .hostname_len = strlen(hostname), + .src_sap = &source, + .src_len = sizeof(source), + .noresvport = noresvport, + }; + + dprintk("lockd: %s(host='%s', vers=%u, proto=%s)\n", __func__, + (hostname ? hostname : "<none>"), version, + (protocol == IPPROTO_UDP ? "udp" : "tcp")); + + return nlm_lookup_host(&ni); +} + +/** + * 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) +{ + struct sockaddr_in sin = { + .sin_family = AF_INET, + }; + struct sockaddr_in6 sin6 = { + .sin6_family = AF_INET6, + }; + 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, + .src_len = rqstp->rq_addrlen, + }; + + 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")); + + switch (ni.sap->sa_family) { + case AF_INET: + sin.sin_addr.s_addr = rqstp->rq_daddr.addr.s_addr; + ni.src_sap = (struct sockaddr *)&sin; + break; + case AF_INET6: + ipv6_addr_copy(&sin6.sin6_addr, &rqstp->rq_daddr.addr6); + ni.src_sap = (struct sockaddr *)&sin6; + break; + default: + return NULL; + } + + return nlm_lookup_host(&ni); +} + +/* + * Create the NLM RPC client for an NLM peer + */ +struct rpc_clnt * +nlm_bind_host(struct nlm_host *host) +{ + struct rpc_clnt *clnt; + + dprintk("lockd: nlm_bind_host %s (%s)\n", + host->h_name, host->h_addrbuf); + + /* Lock host handle */ + 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) { + if (time_after_eq(jiffies, host->h_nextrebind)) { + rpc_force_rebind(clnt); + host->h_nextrebind = jiffies + NLM_HOST_REBIND; + dprintk("lockd: next rebind in %lu jiffies\n", + host->h_nextrebind - jiffies); + } + } else { + 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 = { + .protocol = host->h_proto, + .address = nlm_addr(host), + .addrsize = host->h_addrlen, + .saddress = nlm_srcaddr(host), + .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; + + 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; + } + } + + mutex_unlock(&host->h_mutex); + return clnt; +} + +/* + * Force a portmap lookup of the remote lockd port + */ +void +nlm_rebind_host(struct nlm_host *host) +{ + dprintk("lockd: rebind host %s\n", host->h_name); + if (host->h_rpcclnt && time_after_eq(jiffies, host->h_nextrebind)) { + rpc_force_rebind(host->h_rpcclnt); + host->h_nextrebind = jiffies + NLM_HOST_REBIND; + } +} + +/* + * Increment NLM host count + */ +struct nlm_host * nlm_get_host(struct nlm_host *host) +{ + if (host) { + dprintk("lockd: get host %s\n", host->h_name); + atomic_inc(&host->h_count); + host->h_expires = jiffies + NLM_HOST_EXPIRE; + } + return host; +} + +/* + * Release NLM host after use + */ +void nlm_release_host(struct nlm_host *host) +{ + if (host != NULL) { + dprintk("lockd: release host %s\n", host->h_name); + BUG_ON(atomic_read(&host->h_count) < 0); + if (atomic_dec_and_test(&host->h_count)) { + BUG_ON(!list_empty(&host->h_lockowners)); + BUG_ON(!list_empty(&host->h_granted)); + BUG_ON(!list_empty(&host->h_reclaim)); + } + } +} + +/** + * 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_host_rebooted(const struct nlm_reboot *info) +{ + struct hlist_head *chain; + struct hlist_node *pos; + struct nsm_handle *nsm; + struct nlm_host *host; + + nsm = nsm_reboot_lookup(info); + if (unlikely(nsm == NULL)) + return; + + /* 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. + */ +again: mutex_lock(&nlm_host_mutex); + for (chain = nlm_hosts; chain < nlm_hosts + NLM_HOST_NRHASH; ++chain) { + hlist_for_each_entry(host, pos, chain, h_hash) { + 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); + + if (host->h_server) { + /* We're server for this guy, just ditch + * all the locks he held. */ + nlmsvc_free_host_resources(host); + } else { + /* He's the server, initiate lock recovery. */ + nlmclnt_recovery(host); + } + + nlm_release_host(host); + goto again; + } + } + } + mutex_unlock(&nlm_host_mutex); + nsm_release(nsm); +} + +/* + * Shut down the hosts module. + * Note that this routine is called only at server shutdown time. + */ +void +nlm_shutdown_hosts(void) +{ + struct hlist_head *chain; + struct hlist_node *pos; + struct nlm_host *host; + + dprintk("lockd: shutting down host module\n"); + mutex_lock(&nlm_host_mutex); + + /* First, make all hosts eligible for gc */ + dprintk("lockd: nuking all hosts...\n"); + for (chain = nlm_hosts; chain < nlm_hosts + NLM_HOST_NRHASH; ++chain) { + hlist_for_each_entry(host, pos, chain, h_hash) { + 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(); + mutex_unlock(&nlm_host_mutex); + + /* complain if any hosts are left */ + if (nrhosts) { + printk(KERN_WARNING "lockd: couldn't shutdown host module!\n"); + dprintk("lockd: %d hosts left:\n", nrhosts); + for (chain = nlm_hosts; chain < nlm_hosts + NLM_HOST_NRHASH; ++chain) { + hlist_for_each_entry(host, pos, chain, h_hash) { + dprintk(" %s (cnt %d use %d exp %ld)\n", + host->h_name, atomic_read(&host->h_count), + host->h_inuse, host->h_expires); + } + } + } +} + +/* + * Garbage collect any unused NLM hosts. + * This GC combines reference counting for async operations with + * mark & sweep for resources held by remote clients. + */ +static void +nlm_gc_hosts(void) +{ + struct hlist_head *chain; + struct hlist_node *pos, *next; + struct nlm_host *host; + + dprintk("lockd: host garbage collection\n"); + for (chain = nlm_hosts; chain < nlm_hosts + NLM_HOST_NRHASH; ++chain) { + hlist_for_each_entry(host, pos, chain, h_hash) + host->h_inuse = 0; + } + + /* Mark all hosts that hold locks, blocks or shares */ + nlmsvc_mark_resources(); + + for (chain = nlm_hosts; chain < nlm_hosts + NLM_HOST_NRHASH; ++chain) { + hlist_for_each_entry_safe(host, pos, next, chain, h_hash) { + 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); + continue; + } + dprintk("lockd: delete host %s\n", host->h_name); + hlist_del_init(&host->h_hash); + + nlm_destroy_host(host); + nrhosts--; + } + } + + next_gc = jiffies + NLM_HOST_COLLECT; +} diff --git a/fs/lockd/mon.c b/fs/lockd/mon.c new file mode 100644 index 00000000..e3015464 --- /dev/null +++ b/fs/lockd/mon.c @@ -0,0 +1,580 @@ +/* + * linux/fs/lockd/mon.c + * + * The kernel statd client. + * + * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> + */ + +#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/xprtsock.h> +#include <linux/sunrpc/svc.h> +#include <linux/lockd/lockd.h> + +#include <asm/unaligned.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, +}; + +struct nsm_args { + struct nsm_private *priv; + u32 prog; /* RPC callback info */ + u32 vers; + u32 proc; + + char *mon_name; +}; + +struct nsm_res { + u32 status; + u32 state; +}; + +static struct rpc_program nsm_program; +static LIST_HEAD(nsm_handles); +static DEFINE_SPINLOCK(nsm_lock); + +/* + * Local NSM state + */ +u32 __read_mostly nsm_local_state; +int __read_mostly nsm_use_hostnames; + +static inline struct sockaddr *nsm_addr(const struct nsm_handle *nsm) +{ + return (struct sockaddr *)&nsm->sm_addr; +} + +static struct rpc_clnt *nsm_create(void) +{ + struct sockaddr_in sin = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), + }; + struct rpc_create_args args = { + .protocol = XPRT_TRANSPORT_UDP, + .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 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, + }; + struct rpc_message msg = { + .rpc_argp = &args, + .rpc_resp = res, + }; + + clnt = nsm_create(); + if (IS_ERR(clnt)) { + status = PTR_ERR(clnt); + dprintk("lockd: failed to create NSM upcall transport, " + "status=%d\n", status); + goto out; + } + + memset(res, 0, sizeof(*res)); + + msg.rpc_proc = &clnt->cl_procinfo[proc]; + status = rpc_call_sync(clnt, &msg, 0); + if (status < 0) + dprintk("lockd: NSM upcall RPC failed, status=%d\n", + status); + else + status = 0; + rpc_shutdown_client(clnt); + out: + return status; +} + +/** + * 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(const struct nlm_host *host) +{ + struct nsm_handle *nsm = host->h_nsmhandle; + struct nsm_res res; + int status; + + dprintk("lockd: nsm_monitor(%s)\n", nsm->sm_name); + + if (nsm->sm_monitored) + return 0; + + /* + * 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; + + status = nsm_mon_unmon(nsm, NSMPROC_MON, &res); + 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; +} + +/** + * 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. + */ +void nsm_unmonitor(const struct nlm_host *host) +{ + struct nsm_handle *nsm = host->h_nsmhandle; + struct nsm_res res; + int status; + + if (atomic_read(&nsm->sm_count) == 1 + && nsm->sm_monitored && !nsm->sm_sticky) { + dprintk("lockd: nsm_unmonitor(%s)\n", nsm->sm_name); + + status = nsm_mon_unmon(nsm, NSMPROC_UNMON, &res); + 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; + } +} + +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; +} + +/* + * 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 void nsm_init_private(struct nsm_handle *nsm) +{ + 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); +} + +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; + } + +retry: + spin_lock(&nsm_lock); + + 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; + } + + 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 int encode_nsm_string(struct xdr_stream *xdr, const char *string) +{ + const u32 len = strlen(string); + __be32 *p; + + if (unlikely(len > SM_MAXSTRLEN)) + return -EIO; + p = xdr_reserve_space(xdr, sizeof(u32) + len); + if (unlikely(p == NULL)) + return -EIO; + xdr_encode_opaque(p, string, len); + return 0; +} + +/* + * "mon_name" specifies the host to be monitored. + */ +static int encode_mon_name(struct xdr_stream *xdr, const struct nsm_args *argp) +{ + return 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 int encode_my_id(struct xdr_stream *xdr, const struct nsm_args *argp) +{ + int status; + __be32 *p; + + status = encode_nsm_string(xdr, utsname()->nodename); + if (unlikely(status != 0)) + return status; + p = xdr_reserve_space(xdr, 3 * sizeof(u32)); + if (unlikely(p == NULL)) + return -EIO; + *p++ = htonl(argp->prog); + *p++ = htonl(argp->vers); + *p++ = htonl(argp->proc); + return 0; +} + +/* + * The "mon_id" argument specifies the non-private arguments + * of an NSMPROC_MON or NSMPROC_UNMON call. + */ +static int encode_mon_id(struct xdr_stream *xdr, const struct nsm_args *argp) +{ + int status; + + status = encode_mon_name(xdr, argp); + if (unlikely(status != 0)) + return status; + return 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 int encode_priv(struct xdr_stream *xdr, const struct nsm_args *argp) +{ + __be32 *p; + + p = xdr_reserve_space(xdr, SM_PRIV_SIZE); + if (unlikely(p == NULL)) + return -EIO; + xdr_encode_opaque_fixed(p, argp->priv->data, SM_PRIV_SIZE); + return 0; +} + +static int xdr_enc_mon(struct rpc_rqst *req, __be32 *p, + const struct nsm_args *argp) +{ + struct xdr_stream xdr; + int status; + + xdr_init_encode(&xdr, &req->rq_snd_buf, p); + status = encode_mon_id(&xdr, argp); + if (unlikely(status)) + return status; + return encode_priv(&xdr, argp); +} + +static int xdr_enc_unmon(struct rpc_rqst *req, __be32 *p, + const struct nsm_args *argp) +{ + struct xdr_stream xdr; + + xdr_init_encode(&xdr, &req->rq_snd_buf, p); + return encode_mon_id(&xdr, argp); +} + +static int xdr_dec_stat_res(struct rpc_rqst *rqstp, __be32 *p, + struct nsm_res *resp) +{ + struct xdr_stream xdr; + + xdr_init_decode(&xdr, &rqstp->rq_rcv_buf, p); + p = xdr_inline_decode(&xdr, 2 * sizeof(u32)); + if (unlikely(p == NULL)) + return -EIO; + resp->status = ntohl(*p++); + resp->state = ntohl(*p); + + dprintk("lockd: xdr_dec_stat_res status %d state %d\n", + resp->status, resp->state); + return 0; +} + +static int xdr_dec_stat(struct rpc_rqst *rqstp, __be32 *p, + struct nsm_res *resp) +{ + struct xdr_stream xdr; + + xdr_init_decode(&xdr, &rqstp->rq_rcv_buf, p); + p = xdr_inline_decode(&xdr, sizeof(u32)); + if (unlikely(p == NULL)) + return -EIO; + resp->state = ntohl(*p); + + dprintk("lockd: xdr_dec_stat state %d\n", resp->state); + return 0; +} + +#define SM_my_name_sz (1+XDR_QUADLEN(SM_MAXSTRLEN)) +#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 + +static struct rpc_procinfo nsm_procedures[] = { +[NSMPROC_MON] = { + .p_proc = NSMPROC_MON, + .p_encode = (kxdrproc_t)xdr_enc_mon, + .p_decode = (kxdrproc_t)xdr_dec_stat_res, + .p_arglen = SM_mon_sz, + .p_replen = SM_monres_sz, + .p_statidx = NSMPROC_MON, + .p_name = "MONITOR", + }, +[NSMPROC_UNMON] = { + .p_proc = NSMPROC_UNMON, + .p_encode = (kxdrproc_t)xdr_enc_unmon, + .p_decode = (kxdrproc_t)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 = ARRAY_SIZE(nsm_procedures), + .procs = nsm_procedures +}; + +static struct rpc_version * nsm_version[] = { + [1] = &nsm_version1, +}; + +static struct rpc_stat nsm_stats; + +static struct rpc_program nsm_program = { + .name = "statd", + .number = NSM_PROGRAM, + .nrvers = ARRAY_SIZE(nsm_version), + .version = nsm_version, + .stats = &nsm_stats +}; diff --git a/fs/lockd/svc.c b/fs/lockd/svc.c new file mode 100644 index 00000000..f1bacf1a --- /dev/null +++ b/fs/lockd/svc.c @@ -0,0 +1,579 @@ +/* + * linux/fs/lockd/svc.c + * + * This is the central lockd service. + * + * FIXME: Separate the lockd NFS server functionality from the lockd NFS + * client functionality. Oh why didn't Sun create two separate + * services in the first place? + * + * Authors: Olaf Kirch (okir@monad.swb.de) + * + * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/sysctl.h> +#include <linux/moduleparam.h> + +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/uio.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> + +#define NLMDBG_FACILITY NLMDBG_SVC +#define LOCKD_BUFSIZE (1024 + NLMSVC_XDRSIZE) +#define ALLOWED_SIGS (sigmask(SIGKILL)) + +static struct svc_program nlmsvc_program; + +struct nlmsvc_binding * nlmsvc_ops; +EXPORT_SYMBOL_GPL(nlmsvc_ops); + +static DEFINE_MUTEX(nlmsvc_mutex); +static unsigned int nlmsvc_users; +static struct task_struct *nlmsvc_task; +static struct svc_rqst *nlmsvc_rqst; +unsigned long nlmsvc_timeout; + +/* + * These can be set at insmod time (useful for NFS as root filesystem), + * and also changed through the sysctl interface. -- Jamie Lokier, Aug 2003 + */ +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. + */ +static const unsigned long nlm_grace_period_min = 0; +static const unsigned long nlm_grace_period_max = 240; +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 get_lockd_grace_period(void) +{ + /* Note: nlm_timeout should always be nonzero */ + if (nlm_grace_period) + return roundup(nlm_grace_period, nlm_timeout) * HZ; + else + return nlm_timeout * 5 * HZ; +} + +static struct lock_manager lockd_manager = { +}; + +static void grace_ender(struct work_struct *not_used) +{ + locks_end_grace(&lockd_manager); +} + +static DECLARE_DELAYED_WORK(grace_period_end, grace_ender); + +static void set_grace_period(void) +{ + unsigned long grace_period = get_lockd_grace_period(); + + locks_start_grace(&lockd_manager); + cancel_delayed_work_sync(&grace_period_end); + schedule_delayed_work(&grace_period_end, grace_period); +} + +static void restart_grace(void) +{ + if (nlmsvc_ops) { + cancel_delayed_work_sync(&grace_period_end); + locks_end_grace(&lockd_manager); + nlmsvc_invalidate_all(); + set_grace_period(); + } +} + +/* + * This is the lockd kernel thread + */ +static int +lockd(void *vrqstp) +{ + int err = 0, preverr = 0; + struct svc_rqst *rqstp = vrqstp; + + /* try_to_freeze() is called from svc_recv() */ + set_freezable(); + + /* Allow SIGKILL to tell lockd to drop all of its locks */ + allow_signal(SIGKILL); + + dprintk("NFS locking service started (ver " LOCKD_VERSION ").\n"); + + /* + * FIXME: it would be nice if lockd didn't spend its entire life + * running under the BKL. At the very least, it would be good to + * have someone clarify what it's intended to protect here. I've + * seen some handwavy posts about posix locking needing to be + * done under the BKL, but it's far from clear. + */ + lock_kernel(); + + if (!nlm_timeout) + nlm_timeout = LOCKD_DFLT_TIMEO; + nlmsvc_timeout = nlm_timeout * HZ; + + set_grace_period(); + + /* + * The main request loop. We don't terminate until the last + * NFS mount or NFS daemon has gone away. + */ + 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); + restart_grace(); + continue; + } + + timeout = nlmsvc_retry_blocked(); + + /* + * Find a socket with data available and call its + * recvfrom routine. + */ + err = svc_recv(rqstp, timeout); + if (err == -EAGAIN || err == -EINTR) { + preverr = err; + continue; + } + if (err < 0) { + if (err != preverr) { + printk(KERN_WARNING "%s: unexpected error " + "from svc_recv (%d)\n", __func__, err); + preverr = err; + } + schedule_timeout_interruptible(HZ); + continue; + } + preverr = err; + + dprintk("lockd: request from %s\n", + svc_print_addr(rqstp, buf, sizeof(buf))); + + svc_process(rqstp); + } + flush_signals(current); + cancel_delayed_work_sync(&grace_period_end); + locks_end_grace(&lockd_manager); + if (nlmsvc_ops) + nlmsvc_invalidate_all(); + nlm_shutdown_hosts(); + unlock_kernel(); + return 0; +} + +static int create_lockd_listener(struct svc_serv *serv, const char *name, + const int family, const unsigned short port) +{ + struct svc_xprt *xprt; + + xprt = svc_find_xprt(serv, name, family, 0); + if (xprt == NULL) + return svc_create_xprt(serv, name, family, port, + SVC_SOCK_DEFAULTS); + svc_xprt_put(xprt); + return 0; +} + +static int create_lockd_family(struct svc_serv *serv, const int family) +{ + int err; + + err = create_lockd_listener(serv, "udp", family, nlm_udpport); + if (err < 0) + return err; + + return create_lockd_listener(serv, "tcp", family, nlm_tcpport); +} + +/* + * 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. + */ +static int make_socks(struct svc_serv *serv) +{ + static int warned; + int err; + + err = create_lockd_family(serv, PF_INET); + if (err < 0) + goto out_err; + + err = create_lockd_family(serv, 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); + return err; +} + +/* + * Bring up the lockd process if it's not already up. + */ +int lockd_up(void) +{ + struct svc_serv *serv; + int error = 0; + + mutex_lock(&nlmsvc_mutex); + /* + * Check whether we're already up and running. + */ + if (nlmsvc_rqst) + goto out; + + /* + * Sanity check: if there's no pid, + * we should be the first user ... + */ + if (nlmsvc_users) + printk(KERN_WARNING + "lockd_up: no pid, %d users??\n", nlmsvc_users); + + error = -ENOMEM; + serv = svc_create(&nlmsvc_program, LOCKD_BUFSIZE, NULL); + if (!serv) { + printk(KERN_WARNING "lockd_up: create service failed\n"); + goto out; + } + + error = make_socks(serv); + if (error < 0) + goto destroy_and_out; + + /* + * Create the kernel thread and wait for it to start. + */ + nlmsvc_rqst = svc_prepare_thread(serv, &serv->sv_pools[0]); + if (IS_ERR(nlmsvc_rqst)) { + error = PTR_ERR(nlmsvc_rqst); + nlmsvc_rqst = NULL; + printk(KERN_WARNING + "lockd_up: svc_rqst allocation failed, error=%d\n", + error); + goto destroy_and_out; + } + + svc_sock_update_bufs(serv); + serv->sv_maxconn = nlm_max_connections; + + nlmsvc_task = kthread_run(lockd, nlmsvc_rqst, serv->sv_name); + if (IS_ERR(nlmsvc_task)) { + error = PTR_ERR(nlmsvc_task); + svc_exit_thread(nlmsvc_rqst); + nlmsvc_task = NULL; + nlmsvc_rqst = NULL; + printk(KERN_WARNING + "lockd_up: kthread_run failed, error=%d\n", error); + goto destroy_and_out; + } + + /* + * Note: svc_serv structures have an initial use count of 1, + * so we exit through here on both success and failure. + */ +destroy_and_out: + svc_destroy(serv); +out: + if (!error) + nlmsvc_users++; + mutex_unlock(&nlmsvc_mutex); + return error; +} +EXPORT_SYMBOL_GPL(lockd_up); + +/* + * Decrement the user count and bring down lockd if we're the last. + */ +void +lockd_down(void) +{ + mutex_lock(&nlmsvc_mutex); + if (nlmsvc_users) { + if (--nlmsvc_users) + goto out; + } else { + printk(KERN_ERR "lockd_down: no users! task=%p\n", + nlmsvc_task); + BUG(); + } + + if (!nlmsvc_task) { + printk(KERN_ERR "lockd_down: no lockd running.\n"); + BUG(); + } + kthread_stop(nlmsvc_task); + svc_exit_thread(nlmsvc_rqst); + nlmsvc_task = NULL; + nlmsvc_rqst = NULL; +out: + mutex_unlock(&nlmsvc_mutex); +} +EXPORT_SYMBOL_GPL(lockd_down); + +#ifdef CONFIG_SYSCTL + +/* + * Sysctl parameters (same as module parameters, different interface). + */ + +static ctl_table nlm_sysctls[] = { + { + .procname = "nlm_grace_period", + .data = &nlm_grace_period, + .maxlen = sizeof(unsigned long), + .mode = 0644, + .proc_handler = proc_doulongvec_minmax, + .extra1 = (unsigned long *) &nlm_grace_period_min, + .extra2 = (unsigned long *) &nlm_grace_period_max, + }, + { + .procname = "nlm_timeout", + .data = &nlm_timeout, + .maxlen = sizeof(unsigned long), + .mode = 0644, + .proc_handler = proc_doulongvec_minmax, + .extra1 = (unsigned long *) &nlm_timeout_min, + .extra2 = (unsigned long *) &nlm_timeout_max, + }, + { + .procname = "nlm_udpport", + .data = &nlm_udpport, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (int *) &nlm_port_min, + .extra2 = (int *) &nlm_port_max, + }, + { + .procname = "nlm_tcpport", + .data = &nlm_tcpport, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (int *) &nlm_port_min, + .extra2 = (int *) &nlm_port_max, + }, + { + .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[] = { + { + .procname = "nfs", + .mode = 0555, + .child = nlm_sysctls, + }, + { } +}; + +static ctl_table nlm_sysctl_root[] = { + { + .procname = "fs", + .mode = 0555, + .child = nlm_sysctl_dir, + }, + { } +}; + +#endif /* CONFIG_SYSCTL */ + +/* + * Module (and sysfs) parameters. + */ + +#define param_set_min_max(name, type, which_strtol, min, max) \ +static int param_set_##name(const char *val, struct kernel_param *kp) \ +{ \ + char *endp; \ + __typeof__(type) num = which_strtol(val, &endp, 0); \ + if (endp == val || *endp || num < (min) || num > (max)) \ + return -EINVAL; \ + *((int *) kp->arg) = num; \ + return 0; \ +} + +static inline int is_callback(u32 proc) +{ + return proc == NLMPROC_GRANTED + || proc == NLMPROC_GRANTED_MSG + || proc == NLMPROC_TEST_RES + || proc == NLMPROC_LOCK_RES + || proc == NLMPROC_CANCEL_RES + || proc == NLMPROC_UNLOCK_RES + || proc == NLMPROC_NSM_NOTIFY; +} + + +static int lockd_authenticate(struct svc_rqst *rqstp) +{ + rqstp->rq_client = NULL; + switch (rqstp->rq_authop->flavour) { + case RPC_AUTH_NULL: + case RPC_AUTH_UNIX: + if (rqstp->rq_proc == 0) + return SVC_OK; + if (is_callback(rqstp->rq_proc)) { + /* Leave it to individual procedures to + * call nlmsvc_lookup_host(rqstp) + */ + return SVC_OK; + } + return svc_set_client(rqstp); + } + return SVC_DENIED; +} + + +param_set_min_max(port, int, simple_strtol, 0, 65535) +param_set_min_max(grace_period, unsigned long, simple_strtoul, + nlm_grace_period_min, nlm_grace_period_max) +param_set_min_max(timeout, unsigned long, simple_strtoul, + nlm_timeout_min, nlm_timeout_max) + +MODULE_AUTHOR("Olaf Kirch <okir@monad.swb.de>"); +MODULE_DESCRIPTION("NFS file locking service version " LOCKD_VERSION "."); +MODULE_LICENSE("GPL"); + +module_param_call(nlm_grace_period, param_set_grace_period, param_get_ulong, + &nlm_grace_period, 0644); +module_param_call(nlm_timeout, param_set_timeout, param_get_ulong, + &nlm_timeout, 0644); +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); + +/* + * Initialising and terminating the module. + */ + +static int __init init_nlm(void) +{ +#ifdef CONFIG_SYSCTL + nlm_sysctl_table = register_sysctl_table(nlm_sysctl_root); + return nlm_sysctl_table ? 0 : -ENOMEM; +#else + return 0; +#endif +} + +static void __exit exit_nlm(void) +{ + /* FIXME: delete all NLM clients */ + nlm_shutdown_hosts(); +#ifdef CONFIG_SYSCTL + unregister_sysctl_table(nlm_sysctl_table); +#endif +} + +module_init(init_nlm); +module_exit(exit_nlm); + +/* + * Define NLM program and procedures + */ +static struct svc_version nlmsvc_version1 = { + .vs_vers = 1, + .vs_nproc = 17, + .vs_proc = nlmsvc_procedures, + .vs_xdrsize = NLMSVC_XDRSIZE, +}; +static struct svc_version nlmsvc_version3 = { + .vs_vers = 3, + .vs_nproc = 24, + .vs_proc = nlmsvc_procedures, + .vs_xdrsize = NLMSVC_XDRSIZE, +}; +#ifdef CONFIG_LOCKD_V4 +static struct svc_version nlmsvc_version4 = { + .vs_vers = 4, + .vs_nproc = 24, + .vs_proc = nlmsvc_procedures4, + .vs_xdrsize = NLMSVC_XDRSIZE, +}; +#endif +static struct svc_version * nlmsvc_version[] = { + [1] = &nlmsvc_version1, + [3] = &nlmsvc_version3, +#ifdef CONFIG_LOCKD_V4 + [4] = &nlmsvc_version4, +#endif +}; + +static struct svc_stat nlmsvc_stats; + +#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 */ + .pg_vers = nlmsvc_version, /* version table */ + .pg_name = "lockd", /* service name */ + .pg_class = "nfsd", /* share authentication with nfsd */ + .pg_stats = &nlmsvc_stats, /* stats table */ + .pg_authenticate = &lockd_authenticate /* export authentication */ +}; diff --git a/fs/lockd/svc4proc.c b/fs/lockd/svc4proc.c new file mode 100644 index 00000000..031c6569 --- /dev/null +++ b/fs/lockd/svc4proc.c @@ -0,0 +1,506 @@ +/* + * linux/fs/lockd/svc4proc.c + * + * Lockd server procedures. We don't implement the NLM_*_RES + * procedures because we don't use the async procedures. + * + * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> + */ + +#include <linux/types.h> +#include <linux/time.h> +#include <linux/smp_lock.h> +#include <linux/lockd/lockd.h> +#include <linux/lockd/share.h> + +#define NLMDBG_FACILITY NLMDBG_CLIENT + +/* + * Obtain client and file from arguments + */ +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; + __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, 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) + goto no_locks; + *filp = file; + + /* Set up the missing parts of the file_lock structure */ + lock->fl.fl_file = file->f_file; + lock->fl.fl_owner = (fl_owner_t) host; + lock->fl.fl_lmops = &nlmsvc_lock_operations; + } + + return 0; + +no_locks: + nlm_release_host(host); + if (error) + return error; + return nlm_lck_denied_nolocks; +} + +/* + * NULL: Test for presence of service + */ +static __be32 +nlm4svc_proc_null(struct svc_rqst *rqstp, void *argp, void *resp) +{ + dprintk("lockd: NULL called\n"); + return rpc_success; +} + +/* + * TEST: Check for conflicting lock + */ +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; + + /* Obtain client and file */ + if ((resp->status = nlm4svc_retrieve_args(rqstp, argp, &host, &file))) + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; + + /* Now check for conflicting locks */ + 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)); + + nlm_release_host(host); + nlm_release_file(file); + return rc; +} + +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; + + /* Obtain client and file */ + if ((resp->status = nlm4svc_retrieve_args(rqstp, argp, &host, &file))) + 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 + * an old request that time-warped somehow. Any error return would + * do in this case because it's irrelevant anyway. + * + * NB: We don't retrieve the remote host's state yet. + */ + if (host->h_nsmstate && host->h_nsmstate != argp->state) { + resp->status = nlm_lck_denied_nolocks; + } else +#endif + + /* Now try to lock the file */ + 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)); + + nlm_release_host(host); + nlm_release_file(file); + return rc; +} + +static __be32 +nlm4svc_proc_cancel(struct svc_rqst *rqstp, struct nlm_args *argp, + struct nlm_res *resp) +{ + struct nlm_host *host; + struct nlm_file *file; + + dprintk("lockd: CANCEL called\n"); + + resp->cookie = argp->cookie; + + /* Don't accept requests during grace period */ + if (locks_in_grace()) { + 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 resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; + + /* Try to cancel request. */ + resp->status = nlmsvc_cancel_blocked(file, &argp->lock); + + dprintk("lockd: CANCEL status %d\n", ntohl(resp->status)); + nlm_release_host(host); + nlm_release_file(file); + return rpc_success; +} + +/* + * UNLOCK: release a lock + */ +static __be32 +nlm4svc_proc_unlock(struct svc_rqst *rqstp, struct nlm_args *argp, + struct nlm_res *resp) +{ + struct nlm_host *host; + struct nlm_file *file; + + dprintk("lockd: UNLOCK called\n"); + + resp->cookie = argp->cookie; + + /* Don't accept new lock requests during grace period */ + if (locks_in_grace()) { + 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 resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; + + /* Now try to remove the lock */ + resp->status = nlmsvc_unlock(file, &argp->lock); + + dprintk("lockd: UNLOCK status %d\n", ntohl(resp->status)); + nlm_release_host(host); + nlm_release_file(file); + return rpc_success; +} + +/* + * GRANTED: A server calls us to tell that a process' lock request + * was granted + */ +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(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) +{ + lock_kernel(); + nlm_release_call(data); + unlock_kernel(); +} + +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 __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_host *host; + struct nlm_rqst *call; + __be32 stat; + + host = nlmsvc_lookup_host(rqstp, + argp->lock.caller, + argp->lock.len); + if (host == NULL) + return rpc_system_err; + + call = nlm_alloc_call(host); + if (call == NULL) + return rpc_system_err; + + stat = func(rqstp, argp, &call->a_res); + if (stat != 0) { + nlm_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 __be32 nlm4svc_proc_test_msg(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ + 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"); + return nlm4svc_callback(rqstp, NLMPROC_LOCK_RES, argp, nlm4svc_proc_lock); +} + +static __be32 nlm4svc_proc_cancel_msg(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ + dprintk("lockd: CANCEL_MSG called\n"); + return nlm4svc_callback(rqstp, NLMPROC_CANCEL_RES, argp, nlm4svc_proc_cancel); +} + +static __be32 nlm4svc_proc_unlock_msg(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ + dprintk("lockd: UNLOCK_MSG called\n"); + return nlm4svc_callback(rqstp, NLMPROC_UNLOCK_RES, argp, nlm4svc_proc_unlock); +} + +static __be32 nlm4svc_proc_granted_msg(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ + dprintk("lockd: GRANTED_MSG called\n"); + return nlm4svc_callback(rqstp, NLMPROC_GRANTED_RES, argp, nlm4svc_proc_granted); +} + +/* + * SHARE: create a DOS share or alter existing share. + */ +static __be32 +nlm4svc_proc_share(struct svc_rqst *rqstp, struct nlm_args *argp, + struct nlm_res *resp) +{ + struct nlm_host *host; + struct nlm_file *file; + + dprintk("lockd: SHARE called\n"); + + resp->cookie = argp->cookie; + + /* Don't accept new lock requests during grace period */ + if (locks_in_grace() && !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 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); + nlm_release_file(file); + return rpc_success; +} + +/* + * UNSHARE: Release a DOS share. + */ +static __be32 +nlm4svc_proc_unshare(struct svc_rqst *rqstp, struct nlm_args *argp, + struct nlm_res *resp) +{ + struct nlm_host *host; + struct nlm_file *file; + + dprintk("lockd: UNSHARE called\n"); + + resp->cookie = argp->cookie; + + /* Don't accept requests during grace period */ + if (locks_in_grace()) { + 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 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); + nlm_release_file(file); + return rpc_success; +} + +/* + * NM_LOCK: Create an unmonitored lock + */ +static __be32 +nlm4svc_proc_nm_lock(struct svc_rqst *rqstp, struct nlm_args *argp, + struct nlm_res *resp) +{ + dprintk("lockd: NM_LOCK called\n"); + + argp->monitor = 0; /* just clean the monitor flag */ + return nlm4svc_proc_lock(rqstp, argp, resp); +} + +/* + * FREE_ALL: Release all locks and shares held by client + */ +static __be32 +nlm4svc_proc_free_all(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ + struct nlm_host *host; + + /* Obtain client */ + if (nlm4svc_retrieve_args(rqstp, argp, &host, NULL)) + return rpc_success; + + nlmsvc_free_host_resources(host); + nlm_release_host(host); + return rpc_success; +} + +/* + * SM_NOTIFY: private callback from statd (not part of official NLM proto) + */ +static __be32 +nlm4svc_proc_sm_notify(struct svc_rqst *rqstp, struct nlm_reboot *argp, + void *resp) +{ + dprintk("lockd: SM_NOTIFY called\n"); + + 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 __be32 +nlm4svc_proc_granted_res(struct svc_rqst *rqstp, struct nlm_res *argp, + void *resp) +{ + if (!nlmsvc_ops) + return rpc_success; + + dprintk("lockd: GRANTED_RES called\n"); + + nlmsvc_grant_reply(&argp->cookie, argp->status); + return rpc_success; +} + + +/* + * NLM Server procedures. + */ + +#define nlm4svc_encode_norep nlm4svc_encode_void +#define nlm4svc_decode_norep nlm4svc_decode_void +#define nlm4svc_decode_testres nlm4svc_decode_void +#define nlm4svc_decode_lockres nlm4svc_decode_void +#define nlm4svc_decode_unlockres nlm4svc_decode_void +#define nlm4svc_decode_cancelres nlm4svc_decode_void +#define nlm4svc_decode_grantedres nlm4svc_decode_void + +#define nlm4svc_proc_none nlm4svc_proc_null +#define nlm4svc_proc_test_res nlm4svc_proc_null +#define nlm4svc_proc_lock_res nlm4svc_proc_null +#define nlm4svc_proc_cancel_res nlm4svc_proc_null +#define nlm4svc_proc_unlock_res nlm4svc_proc_null + +struct nlm_void { int dummy; }; + +#define PROC(name, xargt, xrest, argt, rest, respsize) \ + { .pc_func = (svc_procfunc) nlm4svc_proc_##name, \ + .pc_decode = (kxdrproc_t) nlm4svc_decode_##xargt, \ + .pc_encode = (kxdrproc_t) nlm4svc_encode_##xrest, \ + .pc_release = NULL, \ + .pc_argsize = sizeof(struct nlm_##argt), \ + .pc_ressize = sizeof(struct nlm_##rest), \ + .pc_xdrressize = respsize, \ + } +#define Ck (1+XDR_QUADLEN(NLM_MAXCOOKIELEN)) /* cookie */ +#define No (1+1024/4) /* netobj */ +#define St 1 /* status */ +#define Rg 4 /* range (offset + length) */ +struct svc_procedure nlmsvc_procedures4[] = { + PROC(null, void, void, void, void, 1), + PROC(test, testargs, testres, args, res, Ck+St+2+No+Rg), + PROC(lock, lockargs, res, args, res, Ck+St), + PROC(cancel, cancargs, res, args, res, Ck+St), + PROC(unlock, unlockargs, res, args, res, Ck+St), + PROC(granted, testargs, res, args, res, Ck+St), + PROC(test_msg, testargs, norep, args, void, 1), + PROC(lock_msg, lockargs, norep, args, void, 1), + PROC(cancel_msg, cancargs, norep, args, void, 1), + PROC(unlock_msg, unlockargs, norep, args, void, 1), + PROC(granted_msg, testargs, norep, args, void, 1), + PROC(test_res, testres, norep, res, void, 1), + PROC(lock_res, lockres, norep, res, void, 1), + PROC(cancel_res, cancelres, norep, res, void, 1), + PROC(unlock_res, unlockres, norep, res, void, 1), + PROC(granted_res, res, norep, res, void, 1), + /* statd callback */ + PROC(sm_notify, reboot, void, reboot, void, 1), + PROC(none, void, void, void, void, 0), + PROC(none, void, void, void, void, 0), + PROC(none, void, void, void, void, 0), + PROC(share, shareargs, shareres, args, res, Ck+St+1), + PROC(unshare, shareargs, shareres, args, res, Ck+St+1), + PROC(nm_lock, lockargs, res, args, res, Ck+St), + PROC(free_all, notify, void, args, void, 1), + +}; diff --git a/fs/lockd/svclock.c b/fs/lockd/svclock.c new file mode 100644 index 00000000..84055d31 --- /dev/null +++ b/fs/lockd/svclock.c @@ -0,0 +1,924 @@ +/* + * linux/fs/lockd/svclock.c + * + * Handling of server-side locks, mostly of the blocked variety. + * This is the ugliest part of lockd because we tread on very thin ice. + * GRANT and CANCEL calls may get stuck, meet in mid-flight, etc. + * IMNSHO introducing the grant callback into the NLM protocol was one + * of the worst ideas Sun ever had. Except maybe for the idea of doing + * NFS file locking at all. + * + * I'm trying hard to avoid race conditions by protecting most accesses + * to a file's list of blocked locks through a semaphore. The global + * list of blocked locks is not protected in this fashion however. + * Therefore, some functions (such as the RPC callback for the async grant + * call) move blocked locks towards the head of the list *while some other + * process might be traversing it*. This should not be a problem in + * practice, because this will only cause functions traversing the list + * to visit some blocks twice. + * + * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> + */ + +#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/lockd/nlm.h> +#include <linux/lockd/lockd.h> +#include <linux/kthread.h> + +#define NLMDBG_FACILITY NLMDBG_SVCLOCK + +#ifdef CONFIG_LOCKD_V4 +#define nlm_deadlock nlm4_deadlock +#else +#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 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 LIST_HEAD(nlm_blocked); + +/* + * Insert a blocked lock into the global list + */ +static void +nlmsvc_insert_block(struct nlm_block *block, unsigned long when) +{ + struct nlm_block *b; + struct list_head *pos; + + dprintk("lockd: nlmsvc_insert_block(%p, %ld)\n", block, when); + 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 ++; + 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 + */ + } + + list_add_tail(&block->b_list, pos); + block->b_when = when; +} + +/* + * Remove a block from the global list + */ +static inline void +nlmsvc_remove_block(struct nlm_block *block) +{ + if (!list_empty(&block->b_list)) { + list_del_init(&block->b_list); + nlmsvc_release_block(block); + } +} + +/* + * Find a block for a given lock + */ +static struct nlm_block * +nlmsvc_lookup_block(struct nlm_file *file, struct nlm_lock *lock) +{ + 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); + 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)); + if (block->b_file == file && nlm_compare_locks(fl, &lock->fl)) { + kref_get(&block->b_count); + return block; + } + } + + return NULL; +} + +static inline int nlm_cookie_match(struct nlm_cookie *a, struct nlm_cookie *b) +{ + if (a->len != b->len) + return 0; + if (memcmp(a->data, b->data, a->len)) + return 0; + return 1; +} + +/* + * Find a block with a given NLM cookie. + */ +static inline struct nlm_block * +nlmsvc_find_block(struct nlm_cookie *cookie) +{ + struct nlm_block *block; + + 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; +} + +/* + * Create a block and initialize it. + * + * Note: we explicitly set the cookie of the grant reply to that of + * the blocked lock request. The spec explicitly mentions that the client + * should _not_ rely on the callback containing the same cookie as the + * 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 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_rqst *call = NULL; + + nlm_get_host(host); + call = nlm_alloc_call(host); + if (call == NULL) + return NULL; + + /* Allocate memory for block, and initialize arguments */ + block = kzalloc(sizeof(*block), GFP_KERNEL); + if (block == NULL) + goto failed; + kref_init(&block->b_count); + INIT_LIST_HEAD(&block->b_list); + INIT_LIST_HEAD(&block->b_flist); + + if (!nlmsvc_setgrantargs(call, lock)) + goto failed_free; + + /* Set notifier function for VFS, and init args */ + 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); + + /* Create and initialize the block */ + 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 */ + list_add(&block->b_flist, &file->f_blocks); + + /* Set up RPC arguments for callback */ + block->b_call = call; + call->a_flags = RPC_TASK_ASYNC; + call->a_block = block; + + return block; + +failed_free: + kfree(block); +failed: + nlm_release_call(call); + return NULL; +} + +/* + * Delete a block. + * It is the caller's responsibility to check whether the file + * can be closed hereafter. + */ +static int nlmsvc_unlink_block(struct nlm_block *block) +{ + int status; + dprintk("lockd: unlinking block %p...\n", block); + + /* Remove block from list */ + status = posix_unblock_lock(block->b_file->f_file, &block->b_call->a_args.lock.fl); + nlmsvc_remove_block(block); + 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 */ + mutex_lock(&file->f_mutex); + list_del_init(&block->b_flist); + mutex_unlock(&file->f_mutex); + + nlmsvc_freegrantargs(block->b_call); + nlm_release_call(block->b_call); + nlm_release_file(block->b_file); + kfree(block->b_fl); + kfree(block); +} + +static void nlmsvc_release_block(struct nlm_block *block) +{ + if (block != NULL) + kref_put(&block->b_count, nlmsvc_free_block); +} + +/* + * Loop over all blocks and delete blocks held by + * a matching host. + */ +void nlmsvc_traverse_blocks(struct nlm_host *host, + struct nlm_file *file, + nlm_host_match_fn_t match) +{ + 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; + } + 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. + */ +__be32 +nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file, + struct nlm_host *host, struct nlm_lock *lock, int wait, + struct nlm_cookie *cookie, int reclaim) +{ + 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_path.dentry->d_inode->i_sb->s_id, + file->f_file->f_path.dentry->d_inode->i_ino, + lock->fl.fl_type, lock->fl.fl_pid, + (long long)lock->fl.fl_start, + (long long)lock->fl.fl_end, + wait); + + /* Lock file against concurrent access */ + 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 (locks_in_grace() && !reclaim) { + ret = nlm_lck_denied_grace_period; + goto out; + } + if (reclaim && !locks_in_grace()) { + ret = nlm_lck_denied_grace_period; + goto out; + } + + 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); +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. + */ +__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 nlm_block *block = NULL; + int error; + __be32 ret; + + dprintk("lockd: nlmsvc_testlock(%s/%ld, ty=%d, %Ld-%Ld)\n", + file->f_file->f_path.dentry->d_inode->i_sb->s_id, + file->f_file->f_path.dentry->d_inode->i_ino, + lock->fl.fl_type, + (long long)lock->fl.fl_start, + (long long)lock->fl.fl_end); + + /* 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()) { + 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; + } + +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; +} + +/* + * Remove a lock. + * This implies a CANCEL call: We send a GRANT_MSG, the client replies + * with a GRANT_RES call which gets lost, and calls UNLOCK immediately + * afterwards. In this case the block will still be there, and hence + * must be removed. + */ +__be32 +nlmsvc_unlock(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_path.dentry->d_inode->i_sb->s_id, + file->f_file->f_path.dentry->d_inode->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); + + lock->fl.fl_type = F_UNLCK; + error = vfs_lock_file(file->f_file, F_SETLK, &lock->fl, NULL); + + return (error < 0)? nlm_lck_denied_nolocks : nlm_granted; +} + +/* + * Cancel a previously blocked request. + * + * A cancel request always overrides any grant that may currently + * be in progress. + * The calling procedure must check whether the file can be closed. + */ +__be32 +nlmsvc_cancel_blocked(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_path.dentry->d_inode->i_sb->s_id, + file->f_file->f_path.dentry->d_inode->i_ino, + lock->fl.fl_pid, + (long long)lock->fl.fl_start, + (long long)lock->fl.fl_end); + + if (locks_in_grace()) + 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 fl_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; + + lock_kernel(); + 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(block, 0); + svc_wake_up(block->b_daemon); + rc = 0; + break; + } + } + unlock_kernel(); + 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. + * + * This function doesn't grant the blocked lock instantly, but rather moves + * the block to the head of nlm_blocked where it can be picked up by lockd. + */ +static void +nlmsvc_notify_blocked(struct file_lock *fl) +{ + struct nlm_block *block; + + dprintk("lockd: VFS unblock notification for block %p\n", fl); + list_for_each_entry(block, &nlm_blocked, b_list) { + if (nlm_compare_locks(&block->b_call->a_args.lock.fl, fl)) { + nlmsvc_insert_block(block, 0); + svc_wake_up(block->b_daemon); + return; + } + } + + printk(KERN_WARNING "lockd: notification for unknown block!\n"); +} + +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; +} + +const struct lock_manager_operations nlmsvc_lock_operations = { + .fl_compare_owner = nlmsvc_same_owner, + .fl_notify = nlmsvc_notify_blocked, + .fl_grant = nlmsvc_grant_deferred, +}; + +/* + * Try to claim a lock that was previously blocked. + * + * Note that we use both the RPC_GRANTED_MSG call _and_ an async + * RPC thread when notifying the client. This seems like overkill... + * Here's why: + * - we don't want to use a synchronous RPC thread, otherwise + * we might find ourselves hanging on a dead portmapper. + * - Some lockd implementations (e.g. HP) don't react to + * RPC_GRANTED calls; they seem to insist on RPC_GRANTED_MSG calls. + */ +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; + int error; + + dprintk("lockd: grant blocked lock %p\n", block); + + kref_get(&block->b_count); + + /* Unlink block request from list */ + 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 + * binding */ + if (block->b_granted) { + nlm_rebind_host(block->b_host); + goto callback; + } + + /* Try the lock operation again */ + lock->fl.fl_flags |= FL_SLEEP; + error = vfs_lock_file(file->f_file, F_SETLK, &lock->fl, NULL); + lock->fl.fl_flags &= ~FL_SLEEP; + + switch (error) { + case 0: + break; + case FILE_LOCK_DEFERRED: + dprintk("lockd: lock still blocked error %d\n", error); + nlmsvc_insert_block(block, NLM_NEVER); + nlmsvc_release_block(block); + return; + default: + printk(KERN_WARNING "lockd: unexpected error %d in %s!\n", + -error, __func__); + nlmsvc_insert_block(block, 10 * HZ); + nlmsvc_release_block(block); + return; + } + +callback: + /* Lock was granted by VFS. */ + dprintk("lockd: GRANTing blocked lock.\n"); + block->b_granted = 1; + + /* 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); + + /* RPC submission failed, wait a bit and retry */ + if (error < 0) + nlmsvc_insert_block(block, 10 * HZ); +} + +/* + * This is the callback from the RPC layer when the NLM_GRANTED_MSG + * RPC call has succeeded or timed out. + * Like all RPC callbacks, it is invoked by the rpciod process, so it + * better not sleep. Therefore, we put the blocked lock on the nlm_blocked + * chain once more in order to have it removed by lockd itself (which can + * then sleep on the file semaphore without disrupting e.g. the nfs client). + */ +static void nlmsvc_grant_callback(struct rpc_task *task, void *data) +{ + struct nlm_rqst *call = data; + struct nlm_block *block = call->a_block; + unsigned long timeout; + + dprintk("lockd: GRANT_MSG RPC callback\n"); + + lock_kernel(); + /* 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 + * can be done, though. */ + if (task->tk_status < 0) { + /* RPC error: Re-insert for retransmission */ + timeout = 10 * HZ; + } else { + /* Call was successful, now wait for client callback */ + timeout = 60 * HZ; + } + nlmsvc_insert_block(block, timeout); + svc_wake_up(block->b_daemon); +out: + unlock_kernel(); +} + +static void nlmsvc_grant_release(void *data) +{ + struct nlm_rqst *call = data; + + lock_kernel(); + nlmsvc_release_block(call->a_block); + unlock_kernel(); +} + +static const struct rpc_call_ops nlmsvc_grant_ops = { + .rpc_call_done = nlmsvc_grant_callback, + .rpc_release = nlmsvc_grant_release, +}; + +/* + * We received a GRANT_RES callback. Try to find the corresponding + * block. + */ +void +nlmsvc_grant_reply(struct nlm_cookie *cookie, __be32 status) +{ + struct nlm_block *block; + + dprintk("grant_reply: looking for cookie %x, s=%d \n", + *(unsigned int *)(cookie->data), status); + if (!(block = nlmsvc_find_block(cookie))) + return; + + if (block) { + if (status == nlm_lck_denied_grace_period) { + /* Try again in a couple of seconds */ + nlmsvc_insert_block(block, 10 * HZ); + } else { + /* Lock is now held by client, or has been rejected. + * In both cases, the block should be removed. */ + nlmsvc_unlink_block(block); + } + } + 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; + } +} + +/* + * Retry all blocked locks that have been notified. This is where lockd + * picks up locks that can be granted, or grant notifications that must + * be retransmitted. + */ +unsigned long +nlmsvc_retry_blocked(void) +{ + unsigned long timeout = MAX_SCHEDULE_TIMEOUT; + struct nlm_block *block; + + while (!list_empty(&nlm_blocked) && !kthread_should_stop()) { + block = list_entry(nlm_blocked.next, struct nlm_block, b_list); + + if (block->b_when == NLM_NEVER) + break; + if (time_after(block->b_when, jiffies)) { + timeout = block->b_when - jiffies; + break; + } + + 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); + } + + return timeout; +} diff --git a/fs/lockd/svcproc.c b/fs/lockd/svcproc.c new file mode 100644 index 00000000..0f2ab741 --- /dev/null +++ b/fs/lockd/svcproc.c @@ -0,0 +1,539 @@ +/* + * linux/fs/lockd/svcproc.c + * + * Lockd server procedures. We don't implement the NLM_*_RES + * procedures because we don't use the async procedures. + * + * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> + */ + +#include <linux/types.h> +#include <linux/time.h> +#include <linux/smp_lock.h> +#include <linux/lockd/lockd.h> +#include <linux/lockd/share.h> + +#define NLMDBG_FACILITY NLMDBG_CLIENT + +#ifdef CONFIG_LOCKD_V4 +static __be32 +cast_to_nlm(__be32 status, u32 vers) +{ + /* Note: status is assumed to be in network byte order !!! */ + if (vers != 4){ + switch (status) { + case nlm_granted: + case nlm_lck_denied: + 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; + break; + default: + status = nlm_lck_denied_nolocks; + } + } + + return (status); +} +#define cast_status(status) (cast_to_nlm(status, rqstp->rq_vers)) +#else +#define cast_status(status) (status) +#endif + +/* + * Obtain client and file from arguments + */ +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; + __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, 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) + goto no_locks; + *filp = file; + + /* Set up the missing parts of the file_lock structure */ + lock->fl.fl_file = file->f_file; + lock->fl.fl_owner = (fl_owner_t) host; + lock->fl.fl_lmops = &nlmsvc_lock_operations; + } + + return 0; + +no_locks: + nlm_release_host(host); + if (error) + return error; + return nlm_lck_denied_nolocks; +} + +/* + * NULL: Test for presence of service + */ +static __be32 +nlmsvc_proc_null(struct svc_rqst *rqstp, void *argp, void *resp) +{ + dprintk("lockd: NULL called\n"); + return rpc_success; +} + +/* + * TEST: Check for conflicting lock + */ +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; + + /* Obtain client and file */ + if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) + return resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; + + /* Now check for conflicting locks */ + 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); + + nlm_release_host(host); + nlm_release_file(file); + return rc; +} + +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; + + /* Obtain client and file */ + if ((resp->status = nlmsvc_retrieve_args(rqstp, argp, &host, &file))) + 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 + * an old request that time-warped somehow. Any error return would + * do in this case because it's irrelevant anyway. + * + * NB: We don't retrieve the remote host's state yet. + */ + if (host->h_nsmstate && host->h_nsmstate != argp->state) { + resp->status = nlm_lck_denied_nolocks; + } else +#endif + + /* Now try to lock the file */ + 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)); + + nlm_release_host(host); + nlm_release_file(file); + return rc; +} + +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; + + dprintk("lockd: CANCEL called\n"); + + resp->cookie = argp->cookie; + + /* Don't accept requests during grace period */ + if (locks_in_grace()) { + 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 resp->status == nlm_drop_reply ? rpc_drop_reply :rpc_success; + + /* Try to cancel request. */ + resp->status = cast_status(nlmsvc_cancel_blocked(file, &argp->lock)); + + dprintk("lockd: CANCEL status %d\n", ntohl(resp->status)); + nlm_release_host(host); + nlm_release_file(file); + return rpc_success; +} + +/* + * UNLOCK: release a lock + */ +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; + + dprintk("lockd: UNLOCK called\n"); + + resp->cookie = argp->cookie; + + /* Don't accept new lock requests during grace period */ + if (locks_in_grace()) { + 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 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)); + + dprintk("lockd: UNLOCK status %d\n", ntohl(resp->status)); + nlm_release_host(host); + nlm_release_file(file); + return rpc_success; +} + +/* + * GRANTED: A server calls us to tell that a process' lock request + * was granted + */ +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(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); +} + +static void nlmsvc_callback_release(void *data) +{ + lock_kernel(); + nlm_release_call(data); + unlock_kernel(); +} + +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 __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_host *host; + struct nlm_rqst *call; + __be32 stat; + + host = nlmsvc_lookup_host(rqstp, + argp->lock.caller, + argp->lock.len); + if (host == NULL) + return rpc_system_err; + + call = nlm_alloc_call(host); + if (call == NULL) + return rpc_system_err; + + stat = func(rqstp, argp, &call->a_res); + if (stat != 0) { + nlm_release_call(call); + 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 __be32 nlmsvc_proc_test_msg(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ + 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"); + return nlmsvc_callback(rqstp, NLMPROC_LOCK_RES, argp, nlmsvc_proc_lock); +} + +static __be32 nlmsvc_proc_cancel_msg(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ + dprintk("lockd: CANCEL_MSG called\n"); + return nlmsvc_callback(rqstp, NLMPROC_CANCEL_RES, argp, nlmsvc_proc_cancel); +} + +static __be32 +nlmsvc_proc_unlock_msg(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ + dprintk("lockd: UNLOCK_MSG called\n"); + return nlmsvc_callback(rqstp, NLMPROC_UNLOCK_RES, argp, nlmsvc_proc_unlock); +} + +static __be32 +nlmsvc_proc_granted_msg(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ + dprintk("lockd: GRANTED_MSG called\n"); + return nlmsvc_callback(rqstp, NLMPROC_GRANTED_RES, argp, nlmsvc_proc_granted); +} + +/* + * SHARE: create a DOS share or alter existing share. + */ +static __be32 +nlmsvc_proc_share(struct svc_rqst *rqstp, struct nlm_args *argp, + struct nlm_res *resp) +{ + struct nlm_host *host; + struct nlm_file *file; + + dprintk("lockd: SHARE called\n"); + + resp->cookie = argp->cookie; + + /* Don't accept new lock requests during grace period */ + if (locks_in_grace() && !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 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); + nlm_release_file(file); + return rpc_success; +} + +/* + * UNSHARE: Release a DOS share. + */ +static __be32 +nlmsvc_proc_unshare(struct svc_rqst *rqstp, struct nlm_args *argp, + struct nlm_res *resp) +{ + struct nlm_host *host; + struct nlm_file *file; + + dprintk("lockd: UNSHARE called\n"); + + resp->cookie = argp->cookie; + + /* Don't accept requests during grace period */ + if (locks_in_grace()) { + 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 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); + nlm_release_file(file); + return rpc_success; +} + +/* + * NM_LOCK: Create an unmonitored lock + */ +static __be32 +nlmsvc_proc_nm_lock(struct svc_rqst *rqstp, struct nlm_args *argp, + struct nlm_res *resp) +{ + dprintk("lockd: NM_LOCK called\n"); + + argp->monitor = 0; /* just clean the monitor flag */ + return nlmsvc_proc_lock(rqstp, argp, resp); +} + +/* + * FREE_ALL: Release all locks and shares held by client + */ +static __be32 +nlmsvc_proc_free_all(struct svc_rqst *rqstp, struct nlm_args *argp, + void *resp) +{ + struct nlm_host *host; + + /* Obtain client */ + if (nlmsvc_retrieve_args(rqstp, argp, &host, NULL)) + return rpc_success; + + nlmsvc_free_host_resources(host); + nlm_release_host(host); + return rpc_success; +} + +/* + * SM_NOTIFY: private callback from statd (not part of official NLM proto) + */ +static __be32 +nlmsvc_proc_sm_notify(struct svc_rqst *rqstp, struct nlm_reboot *argp, + void *resp) +{ + dprintk("lockd: SM_NOTIFY called\n"); + + 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 __be32 +nlmsvc_proc_granted_res(struct svc_rqst *rqstp, struct nlm_res *argp, + void *resp) +{ + if (!nlmsvc_ops) + return rpc_success; + + dprintk("lockd: GRANTED_RES called\n"); + + nlmsvc_grant_reply(&argp->cookie, argp->status); + return rpc_success; +} + +/* + * NLM Server procedures. + */ + +#define nlmsvc_encode_norep nlmsvc_encode_void +#define nlmsvc_decode_norep nlmsvc_decode_void +#define nlmsvc_decode_testres nlmsvc_decode_void +#define nlmsvc_decode_lockres nlmsvc_decode_void +#define nlmsvc_decode_unlockres nlmsvc_decode_void +#define nlmsvc_decode_cancelres nlmsvc_decode_void +#define nlmsvc_decode_grantedres nlmsvc_decode_void + +#define nlmsvc_proc_none nlmsvc_proc_null +#define nlmsvc_proc_test_res nlmsvc_proc_null +#define nlmsvc_proc_lock_res nlmsvc_proc_null +#define nlmsvc_proc_cancel_res nlmsvc_proc_null +#define nlmsvc_proc_unlock_res nlmsvc_proc_null + +struct nlm_void { int dummy; }; + +#define PROC(name, xargt, xrest, argt, rest, respsize) \ + { .pc_func = (svc_procfunc) nlmsvc_proc_##name, \ + .pc_decode = (kxdrproc_t) nlmsvc_decode_##xargt, \ + .pc_encode = (kxdrproc_t) nlmsvc_encode_##xrest, \ + .pc_release = NULL, \ + .pc_argsize = sizeof(struct nlm_##argt), \ + .pc_ressize = sizeof(struct nlm_##rest), \ + .pc_xdrressize = respsize, \ + } + +#define Ck (1+XDR_QUADLEN(NLM_MAXCOOKIELEN)) /* cookie */ +#define St 1 /* status */ +#define No (1+1024/4) /* Net Obj */ +#define Rg 2 /* range - offset + size */ + +struct svc_procedure nlmsvc_procedures[] = { + PROC(null, void, void, void, void, 1), + PROC(test, testargs, testres, args, res, Ck+St+2+No+Rg), + PROC(lock, lockargs, res, args, res, Ck+St), + PROC(cancel, cancargs, res, args, res, Ck+St), + PROC(unlock, unlockargs, res, args, res, Ck+St), + PROC(granted, testargs, res, args, res, Ck+St), + PROC(test_msg, testargs, norep, args, void, 1), + PROC(lock_msg, lockargs, norep, args, void, 1), + PROC(cancel_msg, cancargs, norep, args, void, 1), + PROC(unlock_msg, unlockargs, norep, args, void, 1), + PROC(granted_msg, testargs, norep, args, void, 1), + PROC(test_res, testres, norep, res, void, 1), + PROC(lock_res, lockres, norep, res, void, 1), + PROC(cancel_res, cancelres, norep, res, void, 1), + PROC(unlock_res, unlockres, norep, res, void, 1), + PROC(granted_res, res, norep, res, void, 1), + /* statd callback */ + PROC(sm_notify, reboot, void, reboot, void, 1), + PROC(none, void, void, void, void, 1), + PROC(none, void, void, void, void, 1), + PROC(none, void, void, void, void, 1), + PROC(share, shareargs, shareres, args, res, Ck+St+1), + PROC(unshare, shareargs, shareres, args, res, Ck+St+1), + PROC(nm_lock, lockargs, res, args, res, Ck+St), + PROC(free_all, notify, void, args, void, 0), + +}; diff --git a/fs/lockd/svcshare.c b/fs/lockd/svcshare.c new file mode 100644 index 00000000..b0ae0700 --- /dev/null +++ b/fs/lockd/svcshare.c @@ -0,0 +1,106 @@ +/* + * linux/fs/lockd/svcshare.c + * + * Management of DOS shares. + * + * Copyright (C) 1996 Olaf Kirch <okir@monad.swb.de> + */ + +#include <linux/time.h> +#include <linux/unistd.h> +#include <linux/string.h> +#include <linux/slab.h> + +#include <linux/sunrpc/clnt.h> +#include <linux/sunrpc/svc.h> +#include <linux/lockd/lockd.h> +#include <linux/lockd/share.h> + +static inline int +nlm_cmp_owner(struct nlm_share *share, struct xdr_netobj *oh) +{ + return share->s_owner.len == oh->len + && !memcmp(share->s_owner.data, oh->data, oh->len); +} + +__be32 +nlmsvc_share_file(struct nlm_host *host, struct nlm_file *file, + struct nlm_args *argp) +{ + struct nlm_share *share; + struct xdr_netobj *oh = &argp->lock.oh; + u8 *ohdata; + + for (share = file->f_shares; share; share = share->s_next) { + if (share->s_host == host && nlm_cmp_owner(share, oh)) + goto update; + if ((argp->fsm_access & share->s_mode) + || (argp->fsm_mode & share->s_access )) + return nlm_lck_denied; + } + + share = kmalloc(sizeof(*share) + oh->len, + GFP_KERNEL); + if (share == NULL) + return nlm_lck_denied_nolocks; + + /* Copy owner handle */ + ohdata = (u8 *) (share + 1); + memcpy(ohdata, oh->data, oh->len); + + share->s_file = file; + share->s_host = host; + share->s_owner.data = ohdata; + share->s_owner.len = oh->len; + share->s_next = file->f_shares; + file->f_shares = share; + +update: + share->s_access = argp->fsm_access; + share->s_mode = argp->fsm_mode; + return nlm_granted; +} + +/* + * Delete a share. + */ +__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) != NULL; + shpp = &share->s_next) { + if (share->s_host == host && nlm_cmp_owner(share, oh)) { + *shpp = share->s_next; + kfree(share); + return nlm_granted; + } + } + + /* X/Open spec says return success even if there was no + * corresponding share. */ + return nlm_granted; +} + +/* + * Traverse all shares for a given file, and delete + * those owned by the given (type of) host + */ +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 (match(share->s_host, host)) { + *shpp = share->s_next; + kfree(share); + continue; + } + shpp = &share->s_next; + } +} diff --git a/fs/lockd/svcsubs.c b/fs/lockd/svcsubs.c new file mode 100644 index 00000000..d0ef94cf --- /dev/null +++ b/fs/lockd/svcsubs.c @@ -0,0 +1,439 @@ +/* + * linux/fs/lockd/svcsubs.c + * + * Various support routines for the NLM server. + * + * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de> + */ + +#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/lockd/lockd.h> +#include <linux/lockd/share.h> +#include <linux/module.h> +#include <linux/mount.h> + +#define NLMDBG_FACILITY NLMDBG_SVCSUBS + + +/* + * Global file hash table + */ +#define FILE_HASH_BITS 7 +#define FILE_NRHASH (1<<FILE_HASH_BITS) +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) +{ + u32 *fhp = (u32*)f->data; + + /* print the first 32 bytes of the fh */ + dprintk("lockd: %s (%08x %08x %08x %08x %08x %08x %08x %08x)\n", + msg, fhp[0], fhp[1], fhp[2], fhp[3], + fhp[4], fhp[5], fhp[6], fhp[7]); +} + +static inline void nlm_debug_print_file(char *msg, struct nlm_file *file) +{ + struct inode *inode = file->f_file->f_path.dentry->d_inode; + + dprintk("lockd: %s %s/%ld\n", + msg, inode->i_sb->s_id, inode->i_ino); +} +#else +static inline void nlm_debug_print_fh(char *msg, struct nfs_fh *f) +{ + return; +} + +static inline void nlm_debug_print_file(char *msg, struct nlm_file *file) +{ + return; +} +#endif + +static inline unsigned int file_hash(struct nfs_fh *f) +{ + unsigned int tmp=0; + int i; + for (i=0; i<NFS2_FHSIZE;i++) + tmp += f->data[i]; + return tmp & (FILE_NRHASH - 1); +} + +/* + * Lookup file info. If it doesn't exist, create a file info struct + * and open a (VFS) file for the given inode. + * + * FIXME: + * Note that we open the file O_RDONLY even when creating write locks. + * This is not quite right, but for now, we assume the client performs + * the proper R/W checking. + */ +__be32 +nlm_lookup_file(struct svc_rqst *rqstp, struct nlm_file **result, + struct nfs_fh *f) +{ + struct hlist_node *pos; + struct nlm_file *file; + unsigned int hash; + __be32 nfserr; + + nlm_debug_print_fh("nlm_lookup_file", f); + + hash = file_hash(f); + + /* Lock file table */ + mutex_lock(&nlm_file_mutex); + + hlist_for_each_entry(file, pos, &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 = kzalloc(sizeof(*file), GFP_KERNEL); + if (!file) + goto out_unlock; + + memcpy(&file->f_handle, f, sizeof(struct nfs_fh)); + 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. + * + * We have to make sure we have the right credential to open + * the file. + */ + if ((nfserr = nlmsvc_ops->fopen(rqstp, f, &file->f_file)) != 0) { + dprintk("lockd: open failed (error %d)\n", nfserr); + goto out_free; + } + + hlist_add_head(&file->f_list, &nlm_files[hash]); + +found: + dprintk("lockd: found file %p (count %d)\n", file, file->f_count); + *result = file; + file->f_count++; + nfserr = 0; + +out_unlock: + mutex_unlock(&nlm_file_mutex); + return nfserr; + +out_free: + kfree(file); + goto out_unlock; +} + +/* + * Delete a file after having released all locks, blocks and shares + */ +static inline void +nlm_delete_file(struct nlm_file *file) +{ + nlm_debug_print_file("closing file", file); + 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"); + } +} + +/* + * Loop over all locks on the given file and perform the specified + * action. + */ +static int +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; + struct nlm_host *lockhost; + +again: + file->f_locks = 0; + for (fl = inode->i_flock; fl; fl = fl->fl_next) { + if (fl->fl_lmops != &nlmsvc_lock_operations) + continue; + + /* update current lock count */ + file->f_locks++; + + lockhost = (struct nlm_host *) fl->fl_owner; + if (match(lockhost, host)) { + struct file_lock lock = *fl; + + lock.fl_type = F_UNLCK; + lock.fl_start = 0; + lock.fl_end = OFFSET_MAX; + if (vfs_lock_file(file->f_file, F_SETLK, &lock, NULL) < 0) { + printk("lockd: unlock failure in %s:%d\n", + __FILE__, __LINE__); + return 1; + } + goto again; + } + } + + return 0; +} + +static int +nlmsvc_always_match(void *dummy1, struct nlm_host *dummy2) +{ + return 1; +} + +/* + * Inspect a single file + */ +static inline int +nlm_inspect_file(struct nlm_host *host, struct nlm_file *file, nlm_host_match_fn_t match) +{ + 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; + + for (fl = inode->i_flock; fl; fl = fl->fl_next) { + if (fl->fl_lmops == &nlmsvc_lock_operations) + return 1; + } + file->f_locks = 0; + return 0; +} + +/* + * Loop over all files in the file table. + */ +static int +nlm_traverse_files(void *data, nlm_host_match_fn_t match, + int (*is_failover_file)(void *data, struct nlm_file *file)) +{ + struct hlist_node *pos, *next; + struct nlm_file *file; + int i, ret = 0; + + mutex_lock(&nlm_file_mutex); + for (i = 0; i < FILE_NRHASH; i++) { + hlist_for_each_entry_safe(file, pos, 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(data, file, match)) + ret = 1; + + mutex_lock(&nlm_file_mutex); + file->f_count--; + /* No more references to this file. Let go of it. */ + if (list_empty(&file->f_blocks) && !file->f_locks + && !file->f_shares && !file->f_count) { + hlist_del(&file->f_list); + nlmsvc_ops->fclose(file->f_file); + kfree(file); + } + } + } + mutex_unlock(&nlm_file_mutex); + return ret; +} + +/* + * Release file. If there are no more remote locks on this file, + * close it and free the handle. + * + * Note that we can't do proper reference counting without major + * contortions because the code in fs/locks.c creates, deletes and + * splits locks without notification. Our only way is to walk the + * entire lock list each time we remove a lock. + */ +void +nlm_release_file(struct nlm_file *file) +{ + dprintk("lockd: nlm_release_file(%p, ct = %d)\n", + file, file->f_count); + + /* Lock file table */ + mutex_lock(&nlm_file_mutex); + + /* If there are no more locks etc, delete the file */ + if (--file->f_count == 0 && !nlm_file_inuse(file)) + nlm_delete_file(file); + + mutex_unlock(&nlm_file_mutex); +} + +/* + * Helpers function for resource traversal + * + * nlmsvc_mark_host: + * used by the garbage collector; simply sets h_inuse. + * 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 *dummy) +{ + struct nlm_host *host = data; + + 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) +{ + dprintk("lockd: nlmsvc_mark_resources\n"); + nlm_traverse_files(NULL, nlmsvc_mark_host, NULL); +} + +/* + * Release all resources held by the given client + */ +void +nlmsvc_free_host_resources(struct nlm_host *host) +{ + dprintk("lockd: nlmsvc_free_host_resources\n"); + + if (nlm_traverse_files(host, nlmsvc_same_host, NULL)) { + printk(KERN_WARNING + "lockd: couldn't remove all locks held by %s\n", + host->h_name); + BUG(); + } +} + +/** + * nlmsvc_invalidate_all - remove all locks held for clients + * + * Release all locks held by NFS clients. + * + */ +void +nlmsvc_invalidate_all(void) +{ + /* + * 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.mnt->mnt_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 new file mode 100644 index 00000000..b583ab0a --- /dev/null +++ b/fs/lockd/xdr.c @@ -0,0 +1,630 @@ +/* + * linux/fs/lockd/xdr.c + * + * XDR support for lockd and the lock client. + * + * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> + */ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/nfs.h> + +#include <linux/sunrpc/xdr.h> +#include <linux/sunrpc/clnt.h> +#include <linux/sunrpc/svc.h> +#include <linux/sunrpc/stats.h> +#include <linux/lockd/lockd.h> + +#define NLMDBG_FACILITY NLMDBG_XDR + + +static inline loff_t +s32_to_loff_t(__s32 offset) +{ + return (loff_t)offset; +} + +static inline __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; +} + +/* + * XDR functions for basic NLM types + */ +static __be32 *nlm_decode_cookie(__be32 *p, struct nlm_cookie *c) +{ + unsigned int len; + + len = ntohl(*p++); + + if(len==0) + { + c->len=4; + memset(c->data, 0, 4); /* hockeypux brain damage */ + } + else if(len<=NLM_MAXCOOKIELEN) + { + c->len=len; + memcpy(c->data, p, len); + p+=XDR_QUADLEN(len); + } + else + { + dprintk("lockd: bad cookie size %d (only cookies under " + "%d bytes are supported.)\n", + len, NLM_MAXCOOKIELEN); + return NULL; + } + return p; +} + +static inline __be32 * +nlm_encode_cookie(__be32 *p, struct nlm_cookie *c) +{ + *p++ = htonl(c->len); + memcpy(p, c->data, c->len); + p+=XDR_QUADLEN(c->len); + return p; +} + +static __be32 * +nlm_decode_fh(__be32 *p, struct nfs_fh *f) +{ + unsigned int len; + + if ((len = ntohl(*p++)) != NFS2_FHSIZE) { + dprintk("lockd: bad fhandle size %d (should be %d)\n", + len, NFS2_FHSIZE); + return NULL; + } + f->size = NFS2_FHSIZE; + memset(f->data, 0, sizeof(f->data)); + memcpy(f->data, p, NFS2_FHSIZE); + return p + XDR_QUADLEN(NFS2_FHSIZE); +} + +static inline __be32 * +nlm_encode_fh(__be32 *p, struct nfs_fh *f) +{ + *p++ = htonl(NFS2_FHSIZE); + memcpy(p, f->data, NFS2_FHSIZE); + return p + XDR_QUADLEN(NFS2_FHSIZE); +} + +/* + * Encode and decode owner handle + */ +static inline __be32 * +nlm_decode_oh(__be32 *p, struct xdr_netobj *oh) +{ + return xdr_decode_netobj(p, oh); +} + +static inline __be32 * +nlm_encode_oh(__be32 *p, struct xdr_netobj *oh) +{ + return xdr_encode_netobj(p, oh); +} + +static __be32 * +nlm_decode_lock(__be32 *p, struct nlm_lock *lock) +{ + struct file_lock *fl = &lock->fl; + s32 start, len, end; + + if (!(p = xdr_decode_string_inplace(p, &lock->caller, + &lock->len, + NLM_MAXSTRLEN)) + || !(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 = (pid_t)lock->svid; + fl->fl_flags = FL_POSIX; + fl->fl_type = F_RDLCK; /* as good as anything else */ + 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 p; +} + +/* + * Encode a lock as part of an NLM call + */ +static __be32 * +nlm_encode_lock(__be32 *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(lock->svid); + *p++ = htonl(start); + *p++ = htonl(len); + + return p; +} + +/* + * Encode result of a TEST/TEST_MSG call + */ +static __be32 * +nlm_encode_testres(__be32 *p, struct nlm_res *resp) +{ + s32 start, len; + + if (!(p = nlm_encode_cookie(p, &resp->cookie))) + return NULL; + *p++ = resp->status; + + if (resp->status == nlm_lck_denied) { + struct file_lock *fl = &resp->lock.fl; + + *p++ = (fl->fl_type == F_RDLCK)? xdr_zero : xdr_one; + *p++ = htonl(resp->lock.svid); + + /* Encode owner handle. */ + if (!(p = xdr_encode_netobj(p, &resp->lock.oh))) + 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(start); + *p++ = htonl(len); + } + + return p; +} + + +/* + * First, the server side XDR functions + */ +int +nlmsvc_decode_testargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) +{ + u32 exclusive; + + if (!(p = nlm_decode_cookie(p, &argp->cookie))) + return 0; + + exclusive = ntohl(*p++); + if (!(p = nlm_decode_lock(p, &argp->lock))) + return 0; + if (exclusive) + argp->lock.fl.fl_type = F_WRLCK; + + return xdr_argsize_check(rqstp, p); +} + +int +nlmsvc_encode_testres(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm_encode_testres(p, resp))) + return 0; + return xdr_ressize_check(rqstp, p); +} + +int +nlmsvc_decode_lockargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) +{ + u32 exclusive; + + if (!(p = nlm_decode_cookie(p, &argp->cookie))) + return 0; + argp->block = ntohl(*p++); + exclusive = ntohl(*p++); + if (!(p = nlm_decode_lock(p, &argp->lock))) + return 0; + if (exclusive) + argp->lock.fl.fl_type = F_WRLCK; + argp->reclaim = ntohl(*p++); + argp->state = ntohl(*p++); + argp->monitor = 1; /* monitor client by default */ + + return xdr_argsize_check(rqstp, p); +} + +int +nlmsvc_decode_cancargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) +{ + u32 exclusive; + + if (!(p = nlm_decode_cookie(p, &argp->cookie))) + return 0; + argp->block = ntohl(*p++); + exclusive = ntohl(*p++); + if (!(p = nlm_decode_lock(p, &argp->lock))) + return 0; + if (exclusive) + argp->lock.fl.fl_type = F_WRLCK; + return xdr_argsize_check(rqstp, p); +} + +int +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))) + return 0; + argp->lock.fl.fl_type = F_UNLCK; + return xdr_argsize_check(rqstp, p); +} + +int +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->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, + &lock->len, NLM_MAXSTRLEN)) + || !(p = nlm_decode_fh(p, &lock->fh)) + || !(p = nlm_decode_oh(p, &lock->oh))) + return 0; + argp->fsm_mode = ntohl(*p++); + argp->fsm_access = ntohl(*p++); + return xdr_argsize_check(rqstp, p); +} + +int +nlmsvc_encode_shareres(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm_encode_cookie(p, &resp->cookie))) + return 0; + *p++ = resp->status; + *p++ = xdr_zero; /* sequence argument */ + return xdr_ressize_check(rqstp, p); +} + +int +nlmsvc_encode_res(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm_encode_cookie(p, &resp->cookie))) + return 0; + *p++ = resp->status; + return xdr_ressize_check(rqstp, p); +} + +int +nlmsvc_decode_notify(struct svc_rqst *rqstp, __be32 *p, struct nlm_args *argp) +{ + struct nlm_lock *lock = &argp->lock; + + if (!(p = xdr_decode_string_inplace(p, &lock->caller, + &lock->len, NLM_MAXSTRLEN))) + return 0; + argp->state = ntohl(*p++); + return xdr_argsize_check(rqstp, p); +} + +int +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++); + 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, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm_decode_cookie(p, &resp->cookie))) + return 0; + resp->status = *p++; + return xdr_argsize_check(rqstp, p); +} + +int +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, __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, __be32 *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, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm_decode_cookie(p, &resp->cookie))) + return -EIO; + resp->status = *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++); + resp->lock.svid = ntohl(*p++); + fl->fl_pid = (pid_t)resp->lock.svid; + 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, __be32 *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, __be32 *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, __be32 *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, __be32 *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, __be32 *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, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm_decode_cookie(p, &resp->cookie))) + return -EIO; + resp->status = *p++; + return 0; +} + +#if (NLMCLNT_OHSIZE > XDR_MAX_NETOBJ) +# error "NLM host name cannot be larger than XDR_MAX_NETOBJ!" +#endif + +/* + * 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(NLMCLNT_OHSIZE) +#define NLM_owner_sz 1+XDR_QUADLEN(NLMCLNT_OHSIZE) +#define NLM_fhandle_sz 1+XDR_QUADLEN(NFS2_FHSIZE) +#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 + +/* + * 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_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), +#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, +}; + +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 = ARRAY_SIZE(nlm_versions), + .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]; + 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 diff --git a/fs/lockd/xdr4.c b/fs/lockd/xdr4.c new file mode 100644 index 00000000..ad9dbbc9 --- /dev/null +++ b/fs/lockd/xdr4.c @@ -0,0 +1,589 @@ +/* + * linux/fs/lockd/xdr4.c + * + * XDR support for lockd and the lock client. + * + * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> + * Copyright (C) 1999, Trond Myklebust <trond.myklebust@fys.uio.no> + */ + +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/nfs.h> + +#include <linux/sunrpc/xdr.h> +#include <linux/sunrpc/clnt.h> +#include <linux/sunrpc/svc.h> +#include <linux/sunrpc/stats.h> +#include <linux/lockd/lockd.h> + +#define NLMDBG_FACILITY NLMDBG_XDR + +static inline loff_t +s64_to_loff_t(__s64 offset) +{ + return (loff_t)offset; +} + + +static inline 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; +} + +/* + * XDR functions for basic NLM types + */ +static __be32 * +nlm4_decode_cookie(__be32 *p, struct nlm_cookie *c) +{ + unsigned int len; + + len = ntohl(*p++); + + if(len==0) + { + c->len=4; + memset(c->data, 0, 4); /* hockeypux brain damage */ + } + else if(len<=NLM_MAXCOOKIELEN) + { + c->len=len; + memcpy(c->data, p, len); + p+=XDR_QUADLEN(len); + } + else + { + dprintk("lockd: bad cookie size %d (only cookies under " + "%d bytes are supported.)\n", + len, NLM_MAXCOOKIELEN); + return NULL; + } + return p; +} + +static __be32 * +nlm4_encode_cookie(__be32 *p, struct nlm_cookie *c) +{ + *p++ = htonl(c->len); + memcpy(p, c->data, c->len); + p+=XDR_QUADLEN(c->len); + return p; +} + +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) { + dprintk("lockd: bad fhandle size %d (should be <=%d)\n", + f->size, NFS_MAXFHSIZE); + return NULL; + } + memcpy(f->data, p, f->size); + return p + XDR_QUADLEN(f->size); +} + +static __be32 * +nlm4_encode_fh(__be32 *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 __be32 * +nlm4_decode_oh(__be32 *p, struct xdr_netobj *oh) +{ + return xdr_decode_netobj(p, oh); +} + +static __be32 * +nlm4_encode_oh(__be32 *p, struct xdr_netobj *oh) +{ + return xdr_encode_netobj(p, oh); +} + +static __be32 * +nlm4_decode_lock(__be32 *p, struct nlm_lock *lock) +{ + struct file_lock *fl = &lock->fl; + __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 = (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); + 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 p; +} + +/* + * Encode a lock as part of an NLM call + */ +static __be32 * +nlm4_encode_lock(__be32 *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(lock->svid); + + 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 __be32 * +nlm4_encode_testres(__be32 *p, struct nlm_res *resp) +{ + s64 start, len; + + dprintk("xdr: before encode_testres (p %p resp %p)\n", p, resp); + if (!(p = nlm4_encode_cookie(p, &resp->cookie))) + return NULL; + *p++ = resp->status; + + if (resp->status == nlm_lck_denied) { + struct file_lock *fl = &resp->lock.fl; + + *p++ = (fl->fl_type == F_RDLCK)? xdr_zero : xdr_one; + *p++ = htonl(resp->lock.svid); + + /* Encode owner handle. */ + if (!(p = xdr_encode_netobj(p, &resp->lock.oh))) + return NULL; + + 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); + 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); + } + + dprintk("xdr: after encode_testres (p %p resp %p)\n", p, resp); + return p; +} + + +/* + * First, the server side XDR functions + */ +int +nlm4svc_decode_testargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) +{ + u32 exclusive; + + if (!(p = nlm4_decode_cookie(p, &argp->cookie))) + return 0; + + exclusive = ntohl(*p++); + if (!(p = nlm4_decode_lock(p, &argp->lock))) + return 0; + if (exclusive) + argp->lock.fl.fl_type = F_WRLCK; + + return xdr_argsize_check(rqstp, p); +} + +int +nlm4svc_encode_testres(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm4_encode_testres(p, resp))) + return 0; + return xdr_ressize_check(rqstp, p); +} + +int +nlm4svc_decode_lockargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) +{ + u32 exclusive; + + if (!(p = nlm4_decode_cookie(p, &argp->cookie))) + return 0; + argp->block = ntohl(*p++); + exclusive = ntohl(*p++); + if (!(p = nlm4_decode_lock(p, &argp->lock))) + return 0; + if (exclusive) + argp->lock.fl.fl_type = F_WRLCK; + argp->reclaim = ntohl(*p++); + argp->state = ntohl(*p++); + argp->monitor = 1; /* monitor client by default */ + + return xdr_argsize_check(rqstp, p); +} + +int +nlm4svc_decode_cancargs(struct svc_rqst *rqstp, __be32 *p, nlm_args *argp) +{ + u32 exclusive; + + if (!(p = nlm4_decode_cookie(p, &argp->cookie))) + return 0; + argp->block = ntohl(*p++); + exclusive = ntohl(*p++); + if (!(p = nlm4_decode_lock(p, &argp->lock))) + return 0; + if (exclusive) + argp->lock.fl.fl_type = F_WRLCK; + return xdr_argsize_check(rqstp, p); +} + +int +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))) + return 0; + argp->lock.fl.fl_type = F_UNLCK; + return xdr_argsize_check(rqstp, p); +} + +int +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->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, + &lock->len, NLM_MAXSTRLEN)) + || !(p = nlm4_decode_fh(p, &lock->fh)) + || !(p = nlm4_decode_oh(p, &lock->oh))) + return 0; + argp->fsm_mode = ntohl(*p++); + argp->fsm_access = ntohl(*p++); + return xdr_argsize_check(rqstp, p); +} + +int +nlm4svc_encode_shareres(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm4_encode_cookie(p, &resp->cookie))) + return 0; + *p++ = resp->status; + *p++ = xdr_zero; /* sequence argument */ + return xdr_ressize_check(rqstp, p); +} + +int +nlm4svc_encode_res(struct svc_rqst *rqstp, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm4_encode_cookie(p, &resp->cookie))) + return 0; + *p++ = resp->status; + return xdr_ressize_check(rqstp, p); +} + +int +nlm4svc_decode_notify(struct svc_rqst *rqstp, __be32 *p, struct nlm_args *argp) +{ + struct nlm_lock *lock = &argp->lock; + + if (!(p = xdr_decode_string_inplace(p, &lock->caller, + &lock->len, NLM_MAXSTRLEN))) + return 0; + argp->state = ntohl(*p++); + return xdr_argsize_check(rqstp, p); +} + +int +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++); + 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, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm4_decode_cookie(p, &resp->cookie))) + return 0; + resp->status = *p++; + return xdr_argsize_check(rqstp, p); +} + +int +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, __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, __be32 *p, void *ptr) +{ + return 0; +} +#endif + +static int +nlm4clt_encode_testargs(struct rpc_rqst *req, __be32 *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, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm4_decode_cookie(p, &resp->cookie))) + return -EIO; + resp->status = *p++; + if (resp->status == nlm_lck_denied) { + struct file_lock *fl = &resp->lock.fl; + u32 excl; + __u64 start, len; + __s64 end; + + memset(&resp->lock, 0, sizeof(resp->lock)); + locks_init_lock(fl); + excl = ntohl(*p++); + resp->lock.svid = ntohl(*p++); + fl->fl_pid = (pid_t)resp->lock.svid; + 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, __be32 *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, __be32 *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, __be32 *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, __be32 *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, __be32 *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, __be32 *p, struct nlm_res *resp) +{ + if (!(p = nlm4_decode_cookie(p, &resp->cookie))) + return -EIO; + resp->status = *p++; + return 0; +} + +#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 + +/* + * 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(NLMCLNT_OHSIZE) +#define NLM4_owner_sz 1+XDR_QUADLEN(NLMCLNT_OHSIZE) +#define NLM4_fhandle_sz 1+XDR_QUADLEN(NFS3_FHSIZE) +#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 + +/* + * 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_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), +#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, +}; |