| /* | |
| * Copyright (C) Sistina Software, Inc. 1997-2003 All rights reserved. | |
| * Copyright (C) 2004-2005 Red Hat, Inc. All rights reserved. | |
| * | |
| * This copyrighted material is made available to anyone wishing to use, | |
| * modify, copy, or redistribute it subject to the terms and conditions | |
| * of the GNU General Public License version 2. | |
| */ | |
| #include "lock_dlm.h" | |
| static char junk_lvb[GDLM_LVB_SIZE]; | |
| static void queue_complete(struct gdlm_lock *lp) | |
| { | |
| struct gdlm_ls *ls = lp->ls; | |
| clear_bit(LFL_ACTIVE, &lp->flags); | |
| spin_lock(&ls->async_lock); | |
| list_add_tail(&lp->clist, &ls->complete); | |
| spin_unlock(&ls->async_lock); | |
| wake_up(&ls->thread_wait); | |
| } | |
| static inline void gdlm_ast(void *astarg) | |
| { | |
| queue_complete(astarg); | |
| } | |
| static inline void gdlm_bast(void *astarg, int mode) | |
| { | |
| struct gdlm_lock *lp = astarg; | |
| struct gdlm_ls *ls = lp->ls; | |
| if (!mode) { | |
| printk(KERN_INFO "lock_dlm: bast mode zero %x,%llx\n", | |
| lp->lockname.ln_type, | |
| (unsigned long long)lp->lockname.ln_number); | |
| return; | |
| } | |
| spin_lock(&ls->async_lock); | |
| if (!lp->bast_mode) { | |
| list_add_tail(&lp->blist, &ls->blocking); | |
| lp->bast_mode = mode; | |
| } else if (lp->bast_mode < mode) | |
| lp->bast_mode = mode; | |
| spin_unlock(&ls->async_lock); | |
| wake_up(&ls->thread_wait); | |
| } | |
| void gdlm_queue_delayed(struct gdlm_lock *lp) | |
| { | |
| struct gdlm_ls *ls = lp->ls; | |
| spin_lock(&ls->async_lock); | |
| list_add_tail(&lp->delay_list, &ls->delayed); | |
| spin_unlock(&ls->async_lock); | |
| } | |
| /* convert gfs lock-state to dlm lock-mode */ | |
| static s16 make_mode(s16 lmstate) | |
| { | |
| switch (lmstate) { | |
| case LM_ST_UNLOCKED: | |
| return DLM_LOCK_NL; | |
| case LM_ST_EXCLUSIVE: | |
| return DLM_LOCK_EX; | |
| case LM_ST_DEFERRED: | |
| return DLM_LOCK_CW; | |
| case LM_ST_SHARED: | |
| return DLM_LOCK_PR; | |
| } | |
| gdlm_assert(0, "unknown LM state %d", lmstate); | |
| return -1; | |
| } | |
| /* convert dlm lock-mode to gfs lock-state */ | |
| s16 gdlm_make_lmstate(s16 dlmmode) | |
| { | |
| switch (dlmmode) { | |
| case DLM_LOCK_IV: | |
| case DLM_LOCK_NL: | |
| return LM_ST_UNLOCKED; | |
| case DLM_LOCK_EX: | |
| return LM_ST_EXCLUSIVE; | |
| case DLM_LOCK_CW: | |
| return LM_ST_DEFERRED; | |
| case DLM_LOCK_PR: | |
| return LM_ST_SHARED; | |
| } | |
| gdlm_assert(0, "unknown DLM mode %d", dlmmode); | |
| return -1; | |
| } | |
| /* verify agreement with GFS on the current lock state, NB: DLM_LOCK_NL and | |
| DLM_LOCK_IV are both considered LM_ST_UNLOCKED by GFS. */ | |
| static void check_cur_state(struct gdlm_lock *lp, unsigned int cur_state) | |
| { | |
| s16 cur = make_mode(cur_state); | |
| if (lp->cur != DLM_LOCK_IV) | |
| gdlm_assert(lp->cur == cur, "%d, %d", lp->cur, cur); | |
| } | |
| static inline unsigned int make_flags(struct gdlm_lock *lp, | |
| unsigned int gfs_flags, | |
| s16 cur, s16 req) | |
| { | |
| unsigned int lkf = 0; | |
| if (gfs_flags & LM_FLAG_TRY) | |
| lkf |= DLM_LKF_NOQUEUE; | |
| if (gfs_flags & LM_FLAG_TRY_1CB) { | |
| lkf |= DLM_LKF_NOQUEUE; | |
| lkf |= DLM_LKF_NOQUEUEBAST; | |
| } | |
| if (gfs_flags & LM_FLAG_PRIORITY) { | |
| lkf |= DLM_LKF_NOORDER; | |
| lkf |= DLM_LKF_HEADQUE; | |
| } | |
| if (gfs_flags & LM_FLAG_ANY) { | |
| if (req == DLM_LOCK_PR) | |
| lkf |= DLM_LKF_ALTCW; | |
| else if (req == DLM_LOCK_CW) | |
| lkf |= DLM_LKF_ALTPR; | |
| } | |
| if (lp->lksb.sb_lkid != 0) { | |
| lkf |= DLM_LKF_CONVERT; | |
| /* Conversion deadlock avoidance by DLM */ | |
| if (!test_bit(LFL_FORCE_PROMOTE, &lp->flags) && | |
| !(lkf & DLM_LKF_NOQUEUE) && | |
| cur > DLM_LOCK_NL && req > DLM_LOCK_NL && cur != req) | |
| lkf |= DLM_LKF_CONVDEADLK; | |
| } | |
| if (lp->lvb) | |
| lkf |= DLM_LKF_VALBLK; | |
| return lkf; | |
| } | |
| /* make_strname - convert GFS lock numbers to a string */ | |
| static inline void make_strname(struct lm_lockname *lockname, | |
| struct gdlm_strname *str) | |
| { | |
| sprintf(str->name, "%8x%16llx", lockname->ln_type, | |
| (unsigned long long)lockname->ln_number); | |
| str->namelen = GDLM_STRNAME_BYTES; | |
| } | |
| static int gdlm_create_lp(struct gdlm_ls *ls, struct lm_lockname *name, | |
| struct gdlm_lock **lpp) | |
| { | |
| struct gdlm_lock *lp; | |
| lp = kzalloc(sizeof(struct gdlm_lock), GFP_KERNEL); | |
| if (!lp) | |
| return -ENOMEM; | |
| lp->lockname = *name; | |
| lp->ls = ls; | |
| lp->cur = DLM_LOCK_IV; | |
| lp->lvb = NULL; | |
| lp->hold_null = NULL; | |
| init_completion(&lp->ast_wait); | |
| INIT_LIST_HEAD(&lp->clist); | |
| INIT_LIST_HEAD(&lp->blist); | |
| INIT_LIST_HEAD(&lp->delay_list); | |
| spin_lock(&ls->async_lock); | |
| list_add(&lp->all_list, &ls->all_locks); | |
| ls->all_locks_count++; | |
| spin_unlock(&ls->async_lock); | |
| *lpp = lp; | |
| return 0; | |
| } | |
| void gdlm_delete_lp(struct gdlm_lock *lp) | |
| { | |
| struct gdlm_ls *ls = lp->ls; | |
| spin_lock(&ls->async_lock); | |
| if (!list_empty(&lp->clist)) | |
| list_del_init(&lp->clist); | |
| if (!list_empty(&lp->blist)) | |
| list_del_init(&lp->blist); | |
| if (!list_empty(&lp->delay_list)) | |
| list_del_init(&lp->delay_list); | |
| gdlm_assert(!list_empty(&lp->all_list), "%x,%llx", lp->lockname.ln_type, | |
| (unsigned long long)lp->lockname.ln_number); | |
| list_del_init(&lp->all_list); | |
| ls->all_locks_count--; | |
| spin_unlock(&ls->async_lock); | |
| kfree(lp); | |
| } | |
| int gdlm_get_lock(void *lockspace, struct lm_lockname *name, | |
| void **lockp) | |
| { | |
| struct gdlm_lock *lp; | |
| int error; | |
| error = gdlm_create_lp(lockspace, name, &lp); | |
| *lockp = lp; | |
| return error; | |
| } | |
| void gdlm_put_lock(void *lock) | |
| { | |
| gdlm_delete_lp(lock); | |
| } | |
| unsigned int gdlm_do_lock(struct gdlm_lock *lp) | |
| { | |
| struct gdlm_ls *ls = lp->ls; | |
| struct gdlm_strname str; | |
| int error, bast = 1; | |
| /* | |
| * When recovery is in progress, delay lock requests for submission | |
| * once recovery is done. Requests for recovery (NOEXP) and unlocks | |
| * can pass. | |
| */ | |
| if (test_bit(DFL_BLOCK_LOCKS, &ls->flags) && | |
| !test_bit(LFL_NOBLOCK, &lp->flags) && lp->req != DLM_LOCK_NL) { | |
| gdlm_queue_delayed(lp); | |
| return LM_OUT_ASYNC; | |
| } | |
| /* | |
| * Submit the actual lock request. | |
| */ | |
| if (test_bit(LFL_NOBAST, &lp->flags)) | |
| bast = 0; | |
| make_strname(&lp->lockname, &str); | |
| set_bit(LFL_ACTIVE, &lp->flags); | |
| log_debug("lk %x,%llx id %x %d,%d %x", lp->lockname.ln_type, | |
| (unsigned long long)lp->lockname.ln_number, lp->lksb.sb_lkid, | |
| lp->cur, lp->req, lp->lkf); | |
| error = dlm_lock(ls->dlm_lockspace, lp->req, &lp->lksb, lp->lkf, | |
| str.name, str.namelen, 0, gdlm_ast, lp, | |
| bast ? gdlm_bast : NULL); | |
| if ((error == -EAGAIN) && (lp->lkf & DLM_LKF_NOQUEUE)) { | |
| lp->lksb.sb_status = -EAGAIN; | |
| queue_complete(lp); | |
| error = 0; | |
| } | |
| if (error) { | |
| log_debug("%s: gdlm_lock %x,%llx err=%d cur=%d req=%d lkf=%x " | |
| "flags=%lx", ls->fsname, lp->lockname.ln_type, | |
| (unsigned long long)lp->lockname.ln_number, error, | |
| lp->cur, lp->req, lp->lkf, lp->flags); | |
| return LM_OUT_ERROR; | |
| } | |
| return LM_OUT_ASYNC; | |
| } | |
| static unsigned int gdlm_do_unlock(struct gdlm_lock *lp) | |
| { | |
| struct gdlm_ls *ls = lp->ls; | |
| unsigned int lkf = 0; | |
| int error; | |
| set_bit(LFL_DLM_UNLOCK, &lp->flags); | |
| set_bit(LFL_ACTIVE, &lp->flags); | |
| if (lp->lvb) | |
| lkf = DLM_LKF_VALBLK; | |
| log_debug("un %x,%llx %x %d %x", lp->lockname.ln_type, | |
| (unsigned long long)lp->lockname.ln_number, | |
| lp->lksb.sb_lkid, lp->cur, lkf); | |
| error = dlm_unlock(ls->dlm_lockspace, lp->lksb.sb_lkid, lkf, NULL, lp); | |
| if (error) { | |
| log_debug("%s: gdlm_unlock %x,%llx err=%d cur=%d req=%d lkf=%x " | |
| "flags=%lx", ls->fsname, lp->lockname.ln_type, | |
| (unsigned long long)lp->lockname.ln_number, error, | |
| lp->cur, lp->req, lp->lkf, lp->flags); | |
| return LM_OUT_ERROR; | |
| } | |
| return LM_OUT_ASYNC; | |
| } | |
| unsigned int gdlm_lock(void *lock, unsigned int cur_state, | |
| unsigned int req_state, unsigned int flags) | |
| { | |
| struct gdlm_lock *lp = lock; | |
| clear_bit(LFL_DLM_CANCEL, &lp->flags); | |
| if (flags & LM_FLAG_NOEXP) | |
| set_bit(LFL_NOBLOCK, &lp->flags); | |
| check_cur_state(lp, cur_state); | |
| lp->req = make_mode(req_state); | |
| lp->lkf = make_flags(lp, flags, lp->cur, lp->req); | |
| return gdlm_do_lock(lp); | |
| } | |
| unsigned int gdlm_unlock(void *lock, unsigned int cur_state) | |
| { | |
| struct gdlm_lock *lp = lock; | |
| clear_bit(LFL_DLM_CANCEL, &lp->flags); | |
| if (lp->cur == DLM_LOCK_IV) | |
| return 0; | |
| return gdlm_do_unlock(lp); | |
| } | |
| void gdlm_cancel(void *lock) | |
| { | |
| struct gdlm_lock *lp = lock; | |
| struct gdlm_ls *ls = lp->ls; | |
| int error, delay_list = 0; | |
| if (test_bit(LFL_DLM_CANCEL, &lp->flags)) | |
| return; | |
| log_info("gdlm_cancel %x,%llx flags %lx", lp->lockname.ln_type, | |
| (unsigned long long)lp->lockname.ln_number, lp->flags); | |
| spin_lock(&ls->async_lock); | |
| if (!list_empty(&lp->delay_list)) { | |
| list_del_init(&lp->delay_list); | |
| delay_list = 1; | |
| } | |
| spin_unlock(&ls->async_lock); | |
| if (delay_list) { | |
| set_bit(LFL_CANCEL, &lp->flags); | |
| set_bit(LFL_ACTIVE, &lp->flags); | |
| queue_complete(lp); | |
| return; | |
| } | |
| if (!test_bit(LFL_ACTIVE, &lp->flags) || | |
| test_bit(LFL_DLM_UNLOCK, &lp->flags)) { | |
| log_info("gdlm_cancel skip %x,%llx flags %lx", | |
| lp->lockname.ln_type, | |
| (unsigned long long)lp->lockname.ln_number, lp->flags); | |
| return; | |
| } | |
| /* the lock is blocked in the dlm */ | |
| set_bit(LFL_DLM_CANCEL, &lp->flags); | |
| set_bit(LFL_ACTIVE, &lp->flags); | |
| error = dlm_unlock(ls->dlm_lockspace, lp->lksb.sb_lkid, DLM_LKF_CANCEL, | |
| NULL, lp); | |
| log_info("gdlm_cancel rv %d %x,%llx flags %lx", error, | |
| lp->lockname.ln_type, | |
| (unsigned long long)lp->lockname.ln_number, lp->flags); | |
| if (error == -EBUSY) | |
| clear_bit(LFL_DLM_CANCEL, &lp->flags); | |
| } | |
| static int gdlm_add_lvb(struct gdlm_lock *lp) | |
| { | |
| char *lvb; | |
| lvb = kzalloc(GDLM_LVB_SIZE, GFP_KERNEL); | |
| if (!lvb) | |
| return -ENOMEM; | |
| lp->lksb.sb_lvbptr = lvb; | |
| lp->lvb = lvb; | |
| return 0; | |
| } | |
| static void gdlm_del_lvb(struct gdlm_lock *lp) | |
| { | |
| kfree(lp->lvb); | |
| lp->lvb = NULL; | |
| lp->lksb.sb_lvbptr = NULL; | |
| } | |
| /* This can do a synchronous dlm request (requiring a lock_dlm thread to get | |
| the completion) because gfs won't call hold_lvb() during a callback (from | |
| the context of a lock_dlm thread). */ | |
| static int hold_null_lock(struct gdlm_lock *lp) | |
| { | |
| struct gdlm_lock *lpn = NULL; | |
| int error; | |
| if (lp->hold_null) { | |
| printk(KERN_INFO "lock_dlm: lvb already held\n"); | |
| return 0; | |
| } | |
| error = gdlm_create_lp(lp->ls, &lp->lockname, &lpn); | |
| if (error) | |
| goto out; | |
| lpn->lksb.sb_lvbptr = junk_lvb; | |
| lpn->lvb = junk_lvb; | |
| lpn->req = DLM_LOCK_NL; | |
| lpn->lkf = DLM_LKF_VALBLK | DLM_LKF_EXPEDITE; | |
| set_bit(LFL_NOBAST, &lpn->flags); | |
| set_bit(LFL_INLOCK, &lpn->flags); | |
| init_completion(&lpn->ast_wait); | |
| gdlm_do_lock(lpn); | |
| wait_for_completion(&lpn->ast_wait); | |
| error = lpn->lksb.sb_status; | |
| if (error) { | |
| printk(KERN_INFO "lock_dlm: hold_null_lock dlm error %d\n", | |
| error); | |
| gdlm_delete_lp(lpn); | |
| lpn = NULL; | |
| } | |
| out: | |
| lp->hold_null = lpn; | |
| return error; | |
| } | |
| /* This cannot do a synchronous dlm request (requiring a lock_dlm thread to get | |
| the completion) because gfs may call unhold_lvb() during a callback (from | |
| the context of a lock_dlm thread) which could cause a deadlock since the | |
| other lock_dlm thread could be engaged in recovery. */ | |
| static void unhold_null_lock(struct gdlm_lock *lp) | |
| { | |
| struct gdlm_lock *lpn = lp->hold_null; | |
| gdlm_assert(lpn, "%x,%llx", lp->lockname.ln_type, | |
| (unsigned long long)lp->lockname.ln_number); | |
| lpn->lksb.sb_lvbptr = NULL; | |
| lpn->lvb = NULL; | |
| set_bit(LFL_UNLOCK_DELETE, &lpn->flags); | |
| gdlm_do_unlock(lpn); | |
| lp->hold_null = NULL; | |
| } | |
| /* Acquire a NL lock because gfs requires the value block to remain | |
| intact on the resource while the lvb is "held" even if it's holding no locks | |
| on the resource. */ | |
| int gdlm_hold_lvb(void *lock, char **lvbp) | |
| { | |
| struct gdlm_lock *lp = lock; | |
| int error; | |
| error = gdlm_add_lvb(lp); | |
| if (error) | |
| return error; | |
| *lvbp = lp->lvb; | |
| error = hold_null_lock(lp); | |
| if (error) | |
| gdlm_del_lvb(lp); | |
| return error; | |
| } | |
| void gdlm_unhold_lvb(void *lock, char *lvb) | |
| { | |
| struct gdlm_lock *lp = lock; | |
| unhold_null_lock(lp); | |
| gdlm_del_lvb(lp); | |
| } | |
| void gdlm_submit_delayed(struct gdlm_ls *ls) | |
| { | |
| struct gdlm_lock *lp, *safe; | |
| spin_lock(&ls->async_lock); | |
| list_for_each_entry_safe(lp, safe, &ls->delayed, delay_list) { | |
| list_del_init(&lp->delay_list); | |
| list_add_tail(&lp->delay_list, &ls->submit); | |
| } | |
| spin_unlock(&ls->async_lock); | |
| wake_up(&ls->thread_wait); | |
| } | |
| int gdlm_release_all_locks(struct gdlm_ls *ls) | |
| { | |
| struct gdlm_lock *lp, *safe; | |
| int count = 0; | |
| spin_lock(&ls->async_lock); | |
| list_for_each_entry_safe(lp, safe, &ls->all_locks, all_list) { | |
| list_del_init(&lp->all_list); | |
| if (lp->lvb && lp->lvb != junk_lvb) | |
| kfree(lp->lvb); | |
| kfree(lp); | |
| count++; | |
| } | |
| spin_unlock(&ls->async_lock); | |
| return count; | |
| } | |