From 9cd29044bd7be430f0d38620a6b0b6a0c017c6c9 Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Fri, 3 Apr 2015 09:04:02 -0400 Subject: [PATCH 1/7] locks: Remove unnecessary IS_POSIX test Since following change commit bd61e0a9c852de2d705b6f1bb2cc54c5774db570 Author: Jeff Layton Date: Fri Jan 16 15:05:55 2015 -0500 locks: convert posix locks to file_lock_context all Posix locks are kept on their a separate list, so the test is redudant. Signed-off-by: Daniel Wagner Cc: Jeff Layton Cc: "J. Bruce Fields" Cc: Alexander Viro Signed-off-by: Jeff Layton --- fs/locks.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/fs/locks.c b/fs/locks.c index 40bc384728c0..4517b8bfca11 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -964,8 +964,6 @@ static int __posix_lock_file(struct inode *inode, struct file_lock *request, str */ if (request->fl_type != F_UNLCK) { list_for_each_entry(fl, &ctx->flc_posix, fl_list) { - if (!IS_POSIX(fl)) - continue; if (!posix_locks_conflict(request, fl)) continue; if (conflock) From 9b8c86956dea44276e2b2bb368f1f34895f4c5ea Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 3 Apr 2015 09:04:02 -0400 Subject: [PATCH 2/7] locks: remove extraneous IS_POSIX and IS_FLOCK tests We know that the locks being passed into this function are of the correct type, now that they live on their own lists. Signed-off-by: Jeff Layton --- fs/locks.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/locks.c b/fs/locks.c index 4517b8bfca11..f88ed4506664 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -730,7 +730,7 @@ static int posix_locks_conflict(struct file_lock *caller_fl, struct file_lock *s /* POSIX locks owned by the same process do not conflict with * each other. */ - if (!IS_POSIX(sys_fl) || posix_same_owner(caller_fl, sys_fl)) + if (posix_same_owner(caller_fl, sys_fl)) return (0); /* Check whether they overlap */ @@ -748,7 +748,7 @@ static int flock_locks_conflict(struct file_lock *caller_fl, struct file_lock *s /* FLOCK locks referring to the same filp do not conflict with * each other. */ - if (!IS_FLOCK(sys_fl) || (caller_fl->fl_file == sys_fl->fl_file)) + if (caller_fl->fl_file == sys_fl->fl_file) return (0); if ((caller_fl->fl_type & LOCK_MAND) || (sys_fl->fl_type & LOCK_MAND)) return 0; From 663d5af750b8c025d0dfea2cf2a4b4a78cafa3a7 Mon Sep 17 00:00:00 2001 From: Daniel Wagner Date: Fri, 3 Apr 2015 09:04:03 -0400 Subject: [PATCH 3/7] locks: Add lockdep assertion for blocked_lock_lock Annonate insert, remove and iterate function that we need blocked_lock_lock held. Signed-off-by: Daniel Wagner Signed-off-by: Jeff Layton --- fs/locks.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/locks.c b/fs/locks.c index f88ed4506664..36cf93f165a8 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -592,11 +592,15 @@ posix_owner_key(struct file_lock *fl) static void locks_insert_global_blocked(struct file_lock *waiter) { + lockdep_assert_held(&blocked_lock_lock); + hash_add(blocked_hash, &waiter->fl_link, posix_owner_key(waiter)); } static void locks_delete_global_blocked(struct file_lock *waiter) { + lockdep_assert_held(&blocked_lock_lock); + hash_del(&waiter->fl_link); } @@ -838,6 +842,8 @@ static int posix_locks_deadlock(struct file_lock *caller_fl, { int i = 0; + lockdep_assert_held(&blocked_lock_lock); + /* * This deadlock detector can't reasonably detect deadlocks with * FL_OFDLCK locks, since they aren't owned by a process, per-se. From 5c1c669a1b2435e071d566b6db1a8e6b26542ba1 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 3 Apr 2015 09:04:03 -0400 Subject: [PATCH 4/7] locks: don't allocate a lock context for an F_UNLCK request In the event that we get an F_UNLCK request on an inode that has no lock context, there is no reason to allocate one. Change locks_get_lock_context to take a "type" pointer and avoid allocating a new context if it's F_UNLCK. Then, fix the callers to return appropriately if that function returns NULL. Signed-off-by: Jeff Layton --- fs/locks.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/fs/locks.c b/fs/locks.c index 36cf93f165a8..54a79883a7f9 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -203,11 +203,11 @@ static struct kmem_cache *flctx_cache __read_mostly; static struct kmem_cache *filelock_cache __read_mostly; static struct file_lock_context * -locks_get_lock_context(struct inode *inode) +locks_get_lock_context(struct inode *inode, int type) { struct file_lock_context *new; - if (likely(inode->i_flctx)) + if (likely(inode->i_flctx) || type == F_UNLCK) goto out; new = kmem_cache_alloc(flctx_cache, GFP_KERNEL); @@ -877,9 +877,12 @@ static int flock_lock_file(struct file *filp, struct file_lock *request) bool found = false; LIST_HEAD(dispose); - ctx = locks_get_lock_context(inode); - if (!ctx) - return -ENOMEM; + ctx = locks_get_lock_context(inode, request->fl_type); + if (!ctx) { + if (request->fl_type != F_UNLCK) + return -ENOMEM; + return (request->fl_flags & FL_EXISTS) ? -ENOENT : 0; + } if (!(request->fl_flags & FL_ACCESS) && (request->fl_type != F_UNLCK)) { new_fl = locks_alloc_lock(); @@ -945,9 +948,9 @@ static int __posix_lock_file(struct inode *inode, struct file_lock *request, str bool added = false; LIST_HEAD(dispose); - ctx = locks_get_lock_context(inode); + ctx = locks_get_lock_context(inode, request->fl_type); if (!ctx) - return -ENOMEM; + return (request->fl_type == F_UNLCK) ? 0 : -ENOMEM; /* * We may need two file_lock structures for this operation, @@ -1609,7 +1612,8 @@ generic_add_lease(struct file *filp, long arg, struct file_lock **flp, void **pr lease = *flp; trace_generic_add_lease(inode, lease); - ctx = locks_get_lock_context(inode); + /* Note that arg is never F_UNLCK here */ + ctx = locks_get_lock_context(inode, arg); if (!ctx) return -ENOMEM; From cae80b305e1c3944746dd93e33e9b2ccd5a490c1 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 3 Apr 2015 09:04:04 -0400 Subject: [PATCH 5/7] locks: change lm_get_owner and lm_put_owner prototypes The current prototypes for these operations are somewhat awkward as they deal with fl_owners but take struct file_lock arguments. In the future, we'll want to be able to take references without necessarily dealing with a struct file_lock. Change them to take fl_owner_t arguments instead and have the callers deal with assigning the values to the file_lock structs. Signed-off-by: Jeff Layton --- fs/locks.c | 8 +++++--- fs/nfsd/nfs4state.c | 18 ++++++++++-------- include/linux/fs.h | 4 ++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/fs/locks.c b/fs/locks.c index 54a79883a7f9..3ebaafb4c587 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -276,8 +276,10 @@ void locks_release_private(struct file_lock *fl) } if (fl->fl_lmops) { - if (fl->fl_lmops->lm_put_owner) - fl->fl_lmops->lm_put_owner(fl); + if (fl->fl_lmops->lm_put_owner) { + fl->fl_lmops->lm_put_owner(fl->fl_owner); + fl->fl_owner = NULL; + } fl->fl_lmops = NULL; } } @@ -333,7 +335,7 @@ void locks_copy_conflock(struct file_lock *new, struct file_lock *fl) if (fl->fl_lmops) { if (fl->fl_lmops->lm_get_owner) - fl->fl_lmops->lm_get_owner(new, fl); + fl->fl_lmops->lm_get_owner(fl->fl_owner); } } EXPORT_SYMBOL(locks_copy_conflock); diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 8ba1d888f1e6..326a545ea7b2 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -4932,20 +4932,22 @@ nfs4_transform_lock_offset(struct file_lock *lock) lock->fl_end = OFFSET_MAX; } -static void nfsd4_fl_get_owner(struct file_lock *dst, struct file_lock *src) +static fl_owner_t +nfsd4_fl_get_owner(fl_owner_t owner) { - struct nfs4_lockowner *lo = (struct nfs4_lockowner *)src->fl_owner; - dst->fl_owner = (fl_owner_t)lockowner(nfs4_get_stateowner(&lo->lo_owner)); + struct nfs4_lockowner *lo = (struct nfs4_lockowner *)owner; + + nfs4_get_stateowner(&lo->lo_owner); + return owner; } -static void nfsd4_fl_put_owner(struct file_lock *fl) +static void +nfsd4_fl_put_owner(fl_owner_t owner) { - struct nfs4_lockowner *lo = (struct nfs4_lockowner *)fl->fl_owner; + struct nfs4_lockowner *lo = (struct nfs4_lockowner *)owner; - if (lo) { + if (lo) nfs4_put_stateowner(&lo->lo_owner); - fl->fl_owner = NULL; - } } static const struct lock_manager_operations nfsd_posix_mng_ops = { diff --git a/include/linux/fs.h b/include/linux/fs.h index f4131e8ead74..e4111a29697e 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -893,8 +893,8 @@ struct file_lock_operations { struct lock_manager_operations { int (*lm_compare_owner)(struct file_lock *, struct file_lock *); unsigned long (*lm_owner_key)(struct file_lock *); - void (*lm_get_owner)(struct file_lock *, struct file_lock *); - void (*lm_put_owner)(struct file_lock *); + fl_owner_t (*lm_get_owner)(fl_owner_t); + void (*lm_put_owner)(fl_owner_t); void (*lm_notify)(struct file_lock *); /* unblock callback */ int (*lm_grant)(struct file_lock *, int); bool (*lm_break)(struct file_lock *); From 3648888e90bb7fe6d0586ec177511e6678ee22c3 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 3 Apr 2015 09:04:04 -0400 Subject: [PATCH 6/7] locks: get rid of WE_CAN_BREAK_LSLK_NOW dead code As Bruce points out, there's no compelling reason to change /proc/locks output at this point. If we did want to do this, then we'd almost certainly want to introduce a new file to display this info (maybe via debugfs?). Let's remove the dead WE_CAN_BREAK_LSLK_NOW ifdef here and just plan to stay with the legacy format. Reported-by: J. Bruce Fields Signed-off-by: Jeff Layton --- fs/locks.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/fs/locks.c b/fs/locks.c index 3ebaafb4c587..16cae1a00851 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -2565,15 +2565,10 @@ static void lock_get_status(struct seq_file *f, struct file_lock *fl, : (fl->fl_type == F_WRLCK) ? "WRITE" : "READ "); } if (inode) { -#ifdef WE_CAN_BREAK_LSLK_NOW - seq_printf(f, "%d %s:%ld ", fl_pid, - inode->i_sb->s_id, inode->i_ino); -#else - /* userspace relies on this representation of dev_t ;-( */ + /* userspace relies on this representation of dev_t */ seq_printf(f, "%d %02x:%02x:%ld ", fl_pid, MAJOR(inode->i_sb->s_dev), MINOR(inode->i_sb->s_dev), inode->i_ino); -#endif } else { seq_printf(f, "%d :0 ", fl_pid); } From 0429c2b5c1c4c8ba6cd563c1964baf3ed238df26 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 3 Apr 2015 09:04:04 -0400 Subject: [PATCH 7/7] locks: use cmpxchg to assign i_flctx pointer During the v3.20/v4.0 cycle, I had originally had the code manage the inode->i_flctx pointer using a compare-and-swap operation instead of the i_lock. Sasha Levin though hit a problem while testing with trinity that made me believe that that wasn't safe. At the time, changing the code to protect the i_flctx pointer seemed to fix the issue, but I now think that was just coincidence. The issue was likely the same race that Kirill Shutemov hit while testing the pre-rc1 v4.0 kernel and that Linus spotted. Due to the way that the spinlock was dropped in the middle of flock_lock_file, you could end up with multiple flock locks for the same struct file on the inode. Reinstate the use of a CAS operation to assign this pointer since it's likely to be more efficient and gets the i_lock completely out of the file locking business. Signed-off-by: Jeff Layton --- fs/locks.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/fs/locks.c b/fs/locks.c index 16cae1a00851..52b780fb5258 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -223,14 +223,7 @@ locks_get_lock_context(struct inode *inode, int type) * Assign the pointer if it's not already assigned. If it is, then * free the context we just allocated. */ - spin_lock(&inode->i_lock); - if (likely(!inode->i_flctx)) { - inode->i_flctx = new; - new = NULL; - } - spin_unlock(&inode->i_lock); - - if (new) + if (cmpxchg(&inode->i_flctx, NULL, new)) kmem_cache_free(flctx_cache, new); out: return inode->i_flctx;