// SPDX-License-Identifier: GPL-2.0 /* * 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 <[email protected]> */ #include <linux/types.h> #include <linux/slab.h> #include <linux/errno.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/sunrpc/clnt.h> #include <linux/sunrpc/svc_xprt.h> #include <linux/lockd/nlm.h> #include <linux/lockd/lockd.h> #include <linux/exportfs.h> #define NLMDBG_FACILITY … #ifdef CONFIG_LOCKD_V4 #define nlm_deadlock … #else #define nlm_deadlock … #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); static DEFINE_SPINLOCK(nlm_blocked_lock); #if IS_ENABLED(CONFIG_SUNRPC_DEBUG) static const char *nlmdbg_cookie2a(const struct nlm_cookie *cookie) { … } #endif /* * Insert a blocked lock into the global list */ static void nlmsvc_insert_block_locked(struct nlm_block *block, unsigned long when) { … } static void nlmsvc_insert_block(struct nlm_block *block, unsigned long when) { … } /* * Remove a block from the global list */ static inline void nlmsvc_remove_block(struct nlm_block *block) { … } /* * Find a block for a given lock */ static struct nlm_block * nlmsvc_lookup_block(struct nlm_file *file, struct nlm_lock *lock) { … } static inline int nlm_cookie_match(struct nlm_cookie *a, struct nlm_cookie *b) { … } /* * Find a block with a given NLM cookie. */ static inline struct nlm_block * nlmsvc_find_block(struct nlm_cookie *cookie) { … } /* * 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) { … } /* * 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) { … } static void nlmsvc_free_block(struct kref *kref) { … } static void nlmsvc_release_block(struct nlm_block *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) { … } static struct nlm_lockowner * nlmsvc_get_lockowner(struct nlm_lockowner *lockowner) { … } void nlmsvc_put_lockowner(struct nlm_lockowner *lockowner) { … } static struct nlm_lockowner *__nlmsvc_find_lockowner(struct nlm_host *host, pid_t pid) { … } static struct nlm_lockowner *nlmsvc_find_lockowner(struct nlm_host *host, pid_t pid) { … } void nlmsvc_release_lockowner(struct nlm_lock *lock) { … } void nlmsvc_locks_init_private(struct file_lock *fl, struct nlm_host *host, pid_t pid) { … } /* * 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) { … } static void nlmsvc_freegrantargs(struct nlm_rqst *call) { … } /* * Deferred lock request handling for non-blocking lock */ static __be32 nlmsvc_defer_lock_rqst(struct svc_rqst *rqstp, struct nlm_block *block) { … } /* * 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) { … } /* * 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) { … } /* * 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 net *net, struct nlm_file *file, struct nlm_lock *lock) { … } /* * 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 net *net, struct nlm_file *file, struct nlm_lock *lock) { … } /* * This is a callback from the filesystem for VFS file lock requests. * It will be used if lm_grant is defined and the filesystem can not * respond to the request immediately. * For 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, int result) { … } static int nlmsvc_grant_deferred(struct file_lock *fl, int result) { … } /* * 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) { … } static fl_owner_t nlmsvc_get_owner(fl_owner_t owner) { … } static void nlmsvc_put_owner(fl_owner_t owner) { … } const struct lock_manager_operations nlmsvc_lock_operations = …; /* * 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) { … } /* * 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) { … } /* * FIXME: nlmsvc_release_block() grabs a mutex. This is not allowed for an * .rpc_release rpc_call_op */ static void nlmsvc_grant_release(void *data) { … } static const struct rpc_call_ops nlmsvc_grant_ops = …; /* * We received a GRANT_RES callback. Try to find the corresponding * block. */ void nlmsvc_grant_reply(struct nlm_cookie *cookie, __be32 status) { … } /* 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) { … } /* * 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. */ void nlmsvc_retry_blocked(struct svc_rqst *rqstp) { … }