📄 svclock.c
字号:
/* * 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/config.h>#include <linux/types.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>#define NLMDBG_FACILITY NLMDBG_SVCLOCK#ifdef CONFIG_LOCKD_V4#define nlm_deadlock nlm4_deadlock#else#define nlm_deadlock nlm_lck_denied#endifstatic void nlmsvc_insert_block(struct nlm_block *block, unsigned long);static int nlmsvc_remove_block(struct nlm_block *block);static void nlmsvc_grant_callback(struct rpc_task *task);static void nlmsvc_notify_blocked(struct file_lock *);/* * The list of blocked locks to retry */static struct nlm_block * nlm_blocked;/* * Insert a blocked lock into the global list */static voidnlmsvc_insert_block(struct nlm_block *block, unsigned long when){ struct nlm_block **bp, *b; dprintk("lockd: nlmsvc_insert_block(%p, %ld)\n", block, when); if (block->b_queued) nlmsvc_remove_block(block); bp = &nlm_blocked; if (when != NLM_NEVER) { if ((when += jiffies) == NLM_NEVER) when ++; while ((b = *bp) && time_before_eq(b->b_when,when)) bp = &b->b_next; } else while ((b = *bp)) bp = &b->b_next; block->b_queued = 1; block->b_when = when; block->b_next = b; *bp = block;}/* * Remove a block from the global list */static intnlmsvc_remove_block(struct nlm_block *block){ struct nlm_block **bp, *b; if (!block->b_queued) return 1; for (bp = &nlm_blocked; (b = *bp); bp = &b->b_next) { if (b == block) { *bp = block->b_next; block->b_queued = 0; return 1; } } return 0;}/* * Find a block for a given lock and optionally remove it from * the list. */static struct nlm_block *nlmsvc_lookup_block(struct nlm_file *file, struct nlm_lock *lock, int remove){ struct nlm_block **head, *block; struct file_lock *fl; dprintk("lockd: nlmsvc_lookup_block f=%p pd=%d %Ld-%Ld ty=%d\n", file, lock->fl.fl_pid, (long long)lock->fl.fl_start, (long long)lock->fl.fl_end, lock->fl.fl_type); for (head = &nlm_blocked; (block = *head); head = &block->b_next) { fl = &block->b_call.a_args.lock.fl; dprintk("lockd: check f=%p pd=%d %Ld-%Ld ty=%d cookie=%x\n", block->b_file, fl->fl_pid, (long long)fl->fl_start, (long long)fl->fl_end, fl->fl_type, *(unsigned int*)(block->b_call.a_args.cookie.data)); if (block->b_file == file && nlm_compare_locks(fl, &lock->fl)) { if (remove) { *head = block->b_next; block->b_queued = 0; } 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; for (block = nlm_blocked; block; block = block->b_next) { dprintk("cookie: head of blocked queue %p, block %p\n", nlm_blocked, block); if (nlm_cookie_match(&block->b_call.a_args.cookie,cookie)) break; } 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. */static inline struct nlm_block *nlmsvc_create_block(struct svc_rqst *rqstp, struct nlm_file *file, struct nlm_lock *lock, struct nlm_cookie *cookie){ struct nlm_block *block; struct nlm_host *host; struct nlm_rqst *call; /* Create host handle for callback */ host = nlmclnt_lookup_host(&rqstp->rq_addr, rqstp->rq_prot, rqstp->rq_vers); if (host == NULL) return NULL; /* Allocate memory for block, and initialize arguments */ if (!(block = (struct nlm_block *) kmalloc(sizeof(*block), GFP_KERNEL))) goto failed; memset(block, 0, sizeof(*block)); locks_init_lock(&block->b_call.a_args.lock.fl); locks_init_lock(&block->b_call.a_res.lock.fl); if (!nlmclnt_setgrantargs(&block->b_call, lock)) goto failed_free; /* Set notifier function for VFS, and init args */ block->b_call.a_args.lock.fl.fl_notify = nlmsvc_notify_blocked; block->b_call.a_args.cookie = *cookie; /* see above */ 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; /* Add to file's list of blocks */ block->b_fnext = file->f_blocks; file->f_blocks = block; /* Set up RPC arguments for callback */ call = &block->b_call; call->a_host = host; call->a_flags = RPC_TASK_ASYNC; return block;failed_free: kfree(block);failed: nlm_release_host(host); return NULL;}/* * Delete a block. If the lock was cancelled or the grant callback * failed, unlock is set to 1. * It is the caller's responsibility to check whether the file * can be closed hereafter. */static voidnlmsvc_delete_block(struct nlm_block *block, int unlock){ struct file_lock *fl = &block->b_call.a_args.lock.fl; struct nlm_file *file = block->b_file; struct nlm_block **bp; dprintk("lockd: deleting block %p...\n", block); /* Remove block from list */ nlmsvc_remove_block(block); /* If granted, unlock it, else remove from inode block list */ if (unlock && block->b_granted) { dprintk("lockd: deleting granted lock\n"); fl->fl_type = F_UNLCK; posix_lock_file(&block->b_file->f_file, fl, 0); block->b_granted = 0; } else { dprintk("lockd: unblocking blocked lock\n"); posix_unblock_lock(fl); } /* If the block is in the middle of a GRANT callback, * don't kill it yet. */ if (block->b_incall) { nlmsvc_insert_block(block, NLM_NEVER); block->b_done = 1; return; } /* Remove block from file's list of blocks */ for (bp = &file->f_blocks; *bp; bp = &(*bp)->b_fnext) { if (*bp == block) { *bp = block->b_fnext; break; } } if (block->b_host) nlm_release_host(block->b_host); nlmclnt_freegrantargs(&block->b_call); kfree(block);}/* * Loop over all blocks and perform the action specified. * (NLM_ACT_CHECK handled by nlmsvc_inspect_file). */intnlmsvc_traverse_blocks(struct nlm_host *host, struct nlm_file *file, int action){ struct nlm_block *block, *next; down(&file->f_sema); for (block = file->f_blocks; block; block = next) { next = block->b_fnext; if (action == NLM_ACT_MARK) block->b_host->h_inuse = 1; else if (action == NLM_ACT_UNLOCK) { if (host == NULL || host == block->b_host) nlmsvc_delete_block(block, 1); } } up(&file->f_sema); return 0;}/* * Attempt to establish a lock, and if it can't be granted, block it * if required. */u32nlmsvc_lock(struct svc_rqst *rqstp, struct nlm_file *file, struct nlm_lock *lock, int wait, struct nlm_cookie *cookie){ struct file_lock *conflock; struct nlm_block *block; int error; dprintk("lockd: nlmsvc_lock(%04x/%ld, ty=%d, pi=%d, %Ld-%Ld, bl=%d)\n", file->f_file.f_dentry->d_inode->i_dev, file->f_file.f_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 */ down(&file->f_sema); /* Get existing block (in case client is busy-waiting) */ block = nlmsvc_lookup_block(file, lock, 0); lock->fl.fl_flags |= FL_LOCKD;again: if (!(conflock = posix_test_lock(&file->f_file, &lock->fl))) { error = posix_lock_file(&file->f_file, &lock->fl, 0); if (block) nlmsvc_delete_block(block, 0); up(&file->f_sema); dprintk("lockd: posix_lock_file returned %d\n", -error); switch(-error) { case 0: return nlm_granted;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -