18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * linux/fs/lockd/clntlock.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Lock handling for the client side NLM implementation
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/types.h>
128c2ecf20Sopenharmony_ci#include <linux/slab.h>
138c2ecf20Sopenharmony_ci#include <linux/time.h>
148c2ecf20Sopenharmony_ci#include <linux/nfs_fs.h>
158c2ecf20Sopenharmony_ci#include <linux/sunrpc/addr.h>
168c2ecf20Sopenharmony_ci#include <linux/sunrpc/svc.h>
178c2ecf20Sopenharmony_ci#include <linux/lockd/lockd.h>
188c2ecf20Sopenharmony_ci#include <linux/kthread.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define NLMDBG_FACILITY		NLMDBG_CLIENT
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci/*
238c2ecf20Sopenharmony_ci * Local function prototypes
248c2ecf20Sopenharmony_ci */
258c2ecf20Sopenharmony_cistatic int			reclaimer(void *ptr);
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci/*
288c2ecf20Sopenharmony_ci * The following functions handle blocking and granting from the
298c2ecf20Sopenharmony_ci * client perspective.
308c2ecf20Sopenharmony_ci */
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci/*
338c2ecf20Sopenharmony_ci * This is the representation of a blocked client lock.
348c2ecf20Sopenharmony_ci */
358c2ecf20Sopenharmony_cistruct nlm_wait {
368c2ecf20Sopenharmony_ci	struct list_head	b_list;		/* linked list */
378c2ecf20Sopenharmony_ci	wait_queue_head_t	b_wait;		/* where to wait on */
388c2ecf20Sopenharmony_ci	struct nlm_host *	b_host;
398c2ecf20Sopenharmony_ci	struct file_lock *	b_lock;		/* local file lock */
408c2ecf20Sopenharmony_ci	unsigned short		b_reclaim;	/* got to reclaim lock */
418c2ecf20Sopenharmony_ci	__be32			b_status;	/* grant callback status */
428c2ecf20Sopenharmony_ci};
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic LIST_HEAD(nlm_blocked);
458c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(nlm_blocked_lock);
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci/**
488c2ecf20Sopenharmony_ci * nlmclnt_init - Set up per-NFS mount point lockd data structures
498c2ecf20Sopenharmony_ci * @nlm_init: pointer to arguments structure
508c2ecf20Sopenharmony_ci *
518c2ecf20Sopenharmony_ci * Returns pointer to an appropriate nlm_host struct,
528c2ecf20Sopenharmony_ci * or an ERR_PTR value.
538c2ecf20Sopenharmony_ci */
548c2ecf20Sopenharmony_cistruct nlm_host *nlmclnt_init(const struct nlmclnt_initdata *nlm_init)
558c2ecf20Sopenharmony_ci{
568c2ecf20Sopenharmony_ci	struct nlm_host *host;
578c2ecf20Sopenharmony_ci	u32 nlm_version = (nlm_init->nfs_version == 2) ? 1 : 4;
588c2ecf20Sopenharmony_ci	int status;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	status = lockd_up(nlm_init->net, nlm_init->cred);
618c2ecf20Sopenharmony_ci	if (status < 0)
628c2ecf20Sopenharmony_ci		return ERR_PTR(status);
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	host = nlmclnt_lookup_host(nlm_init->address, nlm_init->addrlen,
658c2ecf20Sopenharmony_ci				   nlm_init->protocol, nlm_version,
668c2ecf20Sopenharmony_ci				   nlm_init->hostname, nlm_init->noresvport,
678c2ecf20Sopenharmony_ci				   nlm_init->net, nlm_init->cred);
688c2ecf20Sopenharmony_ci	if (host == NULL)
698c2ecf20Sopenharmony_ci		goto out_nohost;
708c2ecf20Sopenharmony_ci	if (host->h_rpcclnt == NULL && nlm_bind_host(host) == NULL)
718c2ecf20Sopenharmony_ci		goto out_nobind;
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	host->h_nlmclnt_ops = nlm_init->nlmclnt_ops;
748c2ecf20Sopenharmony_ci	return host;
758c2ecf20Sopenharmony_ciout_nobind:
768c2ecf20Sopenharmony_ci	nlmclnt_release_host(host);
778c2ecf20Sopenharmony_ciout_nohost:
788c2ecf20Sopenharmony_ci	lockd_down(nlm_init->net);
798c2ecf20Sopenharmony_ci	return ERR_PTR(-ENOLCK);
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nlmclnt_init);
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci/**
848c2ecf20Sopenharmony_ci * nlmclnt_done - Release resources allocated by nlmclnt_init()
858c2ecf20Sopenharmony_ci * @host: nlm_host structure reserved by nlmclnt_init()
868c2ecf20Sopenharmony_ci *
878c2ecf20Sopenharmony_ci */
888c2ecf20Sopenharmony_civoid nlmclnt_done(struct nlm_host *host)
898c2ecf20Sopenharmony_ci{
908c2ecf20Sopenharmony_ci	struct net *net = host->net;
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	nlmclnt_release_host(host);
938c2ecf20Sopenharmony_ci	lockd_down(net);
948c2ecf20Sopenharmony_ci}
958c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(nlmclnt_done);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci/*
988c2ecf20Sopenharmony_ci * Queue up a lock for blocking so that the GRANTED request can see it
998c2ecf20Sopenharmony_ci */
1008c2ecf20Sopenharmony_cistruct nlm_wait *nlmclnt_prepare_block(struct nlm_host *host, struct file_lock *fl)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	struct nlm_wait *block;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	block = kmalloc(sizeof(*block), GFP_KERNEL);
1058c2ecf20Sopenharmony_ci	if (block != NULL) {
1068c2ecf20Sopenharmony_ci		block->b_host = host;
1078c2ecf20Sopenharmony_ci		block->b_lock = fl;
1088c2ecf20Sopenharmony_ci		init_waitqueue_head(&block->b_wait);
1098c2ecf20Sopenharmony_ci		block->b_status = nlm_lck_blocked;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci		spin_lock(&nlm_blocked_lock);
1128c2ecf20Sopenharmony_ci		list_add(&block->b_list, &nlm_blocked);
1138c2ecf20Sopenharmony_ci		spin_unlock(&nlm_blocked_lock);
1148c2ecf20Sopenharmony_ci	}
1158c2ecf20Sopenharmony_ci	return block;
1168c2ecf20Sopenharmony_ci}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_civoid nlmclnt_finish_block(struct nlm_wait *block)
1198c2ecf20Sopenharmony_ci{
1208c2ecf20Sopenharmony_ci	if (block == NULL)
1218c2ecf20Sopenharmony_ci		return;
1228c2ecf20Sopenharmony_ci	spin_lock(&nlm_blocked_lock);
1238c2ecf20Sopenharmony_ci	list_del(&block->b_list);
1248c2ecf20Sopenharmony_ci	spin_unlock(&nlm_blocked_lock);
1258c2ecf20Sopenharmony_ci	kfree(block);
1268c2ecf20Sopenharmony_ci}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci/*
1298c2ecf20Sopenharmony_ci * Block on a lock
1308c2ecf20Sopenharmony_ci */
1318c2ecf20Sopenharmony_ciint nlmclnt_block(struct nlm_wait *block, struct nlm_rqst *req, long timeout)
1328c2ecf20Sopenharmony_ci{
1338c2ecf20Sopenharmony_ci	long ret;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	/* A borken server might ask us to block even if we didn't
1368c2ecf20Sopenharmony_ci	 * request it. Just say no!
1378c2ecf20Sopenharmony_ci	 */
1388c2ecf20Sopenharmony_ci	if (block == NULL)
1398c2ecf20Sopenharmony_ci		return -EAGAIN;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	/* Go to sleep waiting for GRANT callback. Some servers seem
1428c2ecf20Sopenharmony_ci	 * to lose callbacks, however, so we're going to poll from
1438c2ecf20Sopenharmony_ci	 * time to time just to make sure.
1448c2ecf20Sopenharmony_ci	 *
1458c2ecf20Sopenharmony_ci	 * For now, the retry frequency is pretty high; normally
1468c2ecf20Sopenharmony_ci	 * a 1 minute timeout would do. See the comment before
1478c2ecf20Sopenharmony_ci	 * nlmclnt_lock for an explanation.
1488c2ecf20Sopenharmony_ci	 */
1498c2ecf20Sopenharmony_ci	ret = wait_event_interruptible_timeout(block->b_wait,
1508c2ecf20Sopenharmony_ci			block->b_status != nlm_lck_blocked,
1518c2ecf20Sopenharmony_ci			timeout);
1528c2ecf20Sopenharmony_ci	if (ret < 0)
1538c2ecf20Sopenharmony_ci		return -ERESTARTSYS;
1548c2ecf20Sopenharmony_ci	/* Reset the lock status after a server reboot so we resend */
1558c2ecf20Sopenharmony_ci	if (block->b_status == nlm_lck_denied_grace_period)
1568c2ecf20Sopenharmony_ci		block->b_status = nlm_lck_blocked;
1578c2ecf20Sopenharmony_ci	req->a_res.status = block->b_status;
1588c2ecf20Sopenharmony_ci	return 0;
1598c2ecf20Sopenharmony_ci}
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci/*
1628c2ecf20Sopenharmony_ci * The server lockd has called us back to tell us the lock was granted
1638c2ecf20Sopenharmony_ci */
1648c2ecf20Sopenharmony_ci__be32 nlmclnt_grant(const struct sockaddr *addr, const struct nlm_lock *lock)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	const struct file_lock *fl = &lock->fl;
1678c2ecf20Sopenharmony_ci	const struct nfs_fh *fh = &lock->fh;
1688c2ecf20Sopenharmony_ci	struct nlm_wait	*block;
1698c2ecf20Sopenharmony_ci	__be32 res = nlm_lck_denied;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	/*
1728c2ecf20Sopenharmony_ci	 * Look up blocked request based on arguments.
1738c2ecf20Sopenharmony_ci	 * Warning: must not use cookie to match it!
1748c2ecf20Sopenharmony_ci	 */
1758c2ecf20Sopenharmony_ci	spin_lock(&nlm_blocked_lock);
1768c2ecf20Sopenharmony_ci	list_for_each_entry(block, &nlm_blocked, b_list) {
1778c2ecf20Sopenharmony_ci		struct file_lock *fl_blocked = block->b_lock;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci		if (fl_blocked->fl_start != fl->fl_start)
1808c2ecf20Sopenharmony_ci			continue;
1818c2ecf20Sopenharmony_ci		if (fl_blocked->fl_end != fl->fl_end)
1828c2ecf20Sopenharmony_ci			continue;
1838c2ecf20Sopenharmony_ci		/*
1848c2ecf20Sopenharmony_ci		 * Careful! The NLM server will return the 32-bit "pid" that
1858c2ecf20Sopenharmony_ci		 * we put on the wire: in this case the lockowner "pid".
1868c2ecf20Sopenharmony_ci		 */
1878c2ecf20Sopenharmony_ci		if (fl_blocked->fl_u.nfs_fl.owner->pid != lock->svid)
1888c2ecf20Sopenharmony_ci			continue;
1898c2ecf20Sopenharmony_ci		if (!rpc_cmp_addr(nlm_addr(block->b_host), addr))
1908c2ecf20Sopenharmony_ci			continue;
1918c2ecf20Sopenharmony_ci		if (nfs_compare_fh(NFS_FH(locks_inode(fl_blocked->fl_file)), fh) != 0)
1928c2ecf20Sopenharmony_ci			continue;
1938c2ecf20Sopenharmony_ci		/* Alright, we found a lock. Set the return status
1948c2ecf20Sopenharmony_ci		 * and wake up the caller
1958c2ecf20Sopenharmony_ci		 */
1968c2ecf20Sopenharmony_ci		block->b_status = nlm_granted;
1978c2ecf20Sopenharmony_ci		wake_up(&block->b_wait);
1988c2ecf20Sopenharmony_ci		res = nlm_granted;
1998c2ecf20Sopenharmony_ci	}
2008c2ecf20Sopenharmony_ci	spin_unlock(&nlm_blocked_lock);
2018c2ecf20Sopenharmony_ci	return res;
2028c2ecf20Sopenharmony_ci}
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci/*
2058c2ecf20Sopenharmony_ci * The following procedures deal with the recovery of locks after a
2068c2ecf20Sopenharmony_ci * server crash.
2078c2ecf20Sopenharmony_ci */
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci/*
2108c2ecf20Sopenharmony_ci * Reclaim all locks on server host. We do this by spawning a separate
2118c2ecf20Sopenharmony_ci * reclaimer thread.
2128c2ecf20Sopenharmony_ci */
2138c2ecf20Sopenharmony_civoid
2148c2ecf20Sopenharmony_cinlmclnt_recovery(struct nlm_host *host)
2158c2ecf20Sopenharmony_ci{
2168c2ecf20Sopenharmony_ci	struct task_struct *task;
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	if (!host->h_reclaiming++) {
2198c2ecf20Sopenharmony_ci		nlm_get_host(host);
2208c2ecf20Sopenharmony_ci		task = kthread_run(reclaimer, host, "%s-reclaim", host->h_name);
2218c2ecf20Sopenharmony_ci		if (IS_ERR(task))
2228c2ecf20Sopenharmony_ci			printk(KERN_ERR "lockd: unable to spawn reclaimer "
2238c2ecf20Sopenharmony_ci				"thread. Locks for %s won't be reclaimed! "
2248c2ecf20Sopenharmony_ci				"(%ld)\n", host->h_name, PTR_ERR(task));
2258c2ecf20Sopenharmony_ci	}
2268c2ecf20Sopenharmony_ci}
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_cistatic int
2298c2ecf20Sopenharmony_cireclaimer(void *ptr)
2308c2ecf20Sopenharmony_ci{
2318c2ecf20Sopenharmony_ci	struct nlm_host	  *host = (struct nlm_host *) ptr;
2328c2ecf20Sopenharmony_ci	struct nlm_wait	  *block;
2338c2ecf20Sopenharmony_ci	struct nlm_rqst   *req;
2348c2ecf20Sopenharmony_ci	struct file_lock *fl, *next;
2358c2ecf20Sopenharmony_ci	u32 nsmstate;
2368c2ecf20Sopenharmony_ci	struct net *net = host->net;
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	req = kmalloc(sizeof(*req), GFP_KERNEL);
2398c2ecf20Sopenharmony_ci	if (!req)
2408c2ecf20Sopenharmony_ci		return 0;
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	allow_signal(SIGKILL);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	down_write(&host->h_rwsem);
2458c2ecf20Sopenharmony_ci	lockd_up(net, NULL);	/* note: this cannot fail as lockd is already running */
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci	dprintk("lockd: reclaiming locks for host %s\n", host->h_name);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_cirestart:
2508c2ecf20Sopenharmony_ci	nsmstate = host->h_nsmstate;
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci	/* Force a portmap getport - the peer's lockd will
2538c2ecf20Sopenharmony_ci	 * most likely end up on a different port.
2548c2ecf20Sopenharmony_ci	 */
2558c2ecf20Sopenharmony_ci	host->h_nextrebind = jiffies;
2568c2ecf20Sopenharmony_ci	nlm_rebind_host(host);
2578c2ecf20Sopenharmony_ci
2588c2ecf20Sopenharmony_ci	/* First, reclaim all locks that have been granted. */
2598c2ecf20Sopenharmony_ci	list_splice_init(&host->h_granted, &host->h_reclaim);
2608c2ecf20Sopenharmony_ci	list_for_each_entry_safe(fl, next, &host->h_reclaim, fl_u.nfs_fl.list) {
2618c2ecf20Sopenharmony_ci		list_del_init(&fl->fl_u.nfs_fl.list);
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci		/*
2648c2ecf20Sopenharmony_ci		 * sending this thread a SIGKILL will result in any unreclaimed
2658c2ecf20Sopenharmony_ci		 * locks being removed from the h_granted list. This means that
2668c2ecf20Sopenharmony_ci		 * the kernel will not attempt to reclaim them again if a new
2678c2ecf20Sopenharmony_ci		 * reclaimer thread is spawned for this host.
2688c2ecf20Sopenharmony_ci		 */
2698c2ecf20Sopenharmony_ci		if (signalled())
2708c2ecf20Sopenharmony_ci			continue;
2718c2ecf20Sopenharmony_ci		if (nlmclnt_reclaim(host, fl, req) != 0)
2728c2ecf20Sopenharmony_ci			continue;
2738c2ecf20Sopenharmony_ci		list_add_tail(&fl->fl_u.nfs_fl.list, &host->h_granted);
2748c2ecf20Sopenharmony_ci		if (host->h_nsmstate != nsmstate) {
2758c2ecf20Sopenharmony_ci			/* Argh! The server rebooted again! */
2768c2ecf20Sopenharmony_ci			goto restart;
2778c2ecf20Sopenharmony_ci		}
2788c2ecf20Sopenharmony_ci	}
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	host->h_reclaiming = 0;
2818c2ecf20Sopenharmony_ci	up_write(&host->h_rwsem);
2828c2ecf20Sopenharmony_ci	dprintk("NLM: done reclaiming locks for host %s\n", host->h_name);
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	/* Now, wake up all processes that sleep on a blocked lock */
2858c2ecf20Sopenharmony_ci	spin_lock(&nlm_blocked_lock);
2868c2ecf20Sopenharmony_ci	list_for_each_entry(block, &nlm_blocked, b_list) {
2878c2ecf20Sopenharmony_ci		if (block->b_host == host) {
2888c2ecf20Sopenharmony_ci			block->b_status = nlm_lck_denied_grace_period;
2898c2ecf20Sopenharmony_ci			wake_up(&block->b_wait);
2908c2ecf20Sopenharmony_ci		}
2918c2ecf20Sopenharmony_ci	}
2928c2ecf20Sopenharmony_ci	spin_unlock(&nlm_blocked_lock);
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	/* Release host handle after use */
2958c2ecf20Sopenharmony_ci	nlmclnt_release_host(host);
2968c2ecf20Sopenharmony_ci	lockd_down(net);
2978c2ecf20Sopenharmony_ci	kfree(req);
2988c2ecf20Sopenharmony_ci	return 0;
2998c2ecf20Sopenharmony_ci}
300