From b5971afa0b33361667bc88f3e0eb3fc31f778dc6 Mon Sep 17 00:00:00 2001 From: Kinglong Mee Date: Fri, 22 Aug 2014 10:18:43 -0400 Subject: NFSD: New helper nfs4_get_stateowner() for atomic_inc sop reference v5: same as the first version Reviewed-by: Jeff Layton Signed-off-by: Kinglong Mee Signed-off-by: Jeff Layton --- fs/nfsd/nfs4state.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'fs/nfsd/nfs4state.c') diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index e773036b03d0..2d03a4188671 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -216,6 +216,13 @@ static void nfsd4_put_session(struct nfsd4_session *ses) spin_unlock(&nn->client_lock); } +static inline struct nfs4_stateowner * +nfs4_get_stateowner(struct nfs4_stateowner *sop) +{ + atomic_inc(&sop->so_count); + return sop; +} + static int same_owner_str(struct nfs4_stateowner *sop, struct xdr_netobj *owner) { @@ -235,10 +242,8 @@ find_openstateowner_str_locked(unsigned int hashval, struct nfsd4_open *open, so_strhash) { if (!so->so_is_open_owner) continue; - if (same_owner_str(so, &open->op_owner)) { - atomic_inc(&so->so_count); - return openowner(so); - } + if (same_owner_str(so, &open->op_owner)) + return openowner(nfs4_get_stateowner(so)); } return NULL; } @@ -1651,7 +1656,7 @@ __destroy_client(struct nfs4_client *clp) } while (!list_empty(&clp->cl_openowners)) { oo = list_entry(clp->cl_openowners.next, struct nfs4_openowner, oo_perclient); - atomic_inc(&oo->oo_owner.so_count); + nfs4_get_stateowner(&oo->oo_owner); release_openowner(oo); } nfsd4_shutdown_callback(clp); @@ -3132,8 +3137,7 @@ static void nfsd4_cstate_assign_replay(struct nfsd4_compound_state *cstate, { if (!nfsd4_has_session(cstate)) { mutex_lock(&so->so_replay.rp_mutex); - cstate->replay_owner = so; - atomic_inc(&so->so_count); + cstate->replay_owner = nfs4_get_stateowner(so); } } @@ -3232,8 +3236,7 @@ static void init_open_stateid(struct nfs4_ol_stateid *stp, struct nfs4_file *fp, atomic_inc(&stp->st_stid.sc_count); stp->st_stid.sc_type = NFS4_OPEN_STID; INIT_LIST_HEAD(&stp->st_locks); - stp->st_stateowner = &oo->oo_owner; - atomic_inc(&stp->st_stateowner->so_count); + stp->st_stateowner = nfs4_get_stateowner(&oo->oo_owner); get_nfs4_file(fp); stp->st_stid.sc_file = fp; stp->st_access_bmap = 0; @@ -4921,10 +4924,8 @@ find_lockowner_str_locked(clientid_t *clid, struct xdr_netobj *owner, so_strhash) { if (so->so_is_open_owner) continue; - if (!same_owner_str(so, owner)) - continue; - atomic_inc(&so->so_count); - return lockowner(so); + if (same_owner_str(so, owner)) + return lockowner(nfs4_get_stateowner(so)); } return NULL; } @@ -5003,8 +5004,7 @@ init_lock_stateid(struct nfs4_ol_stateid *stp, struct nfs4_lockowner *lo, atomic_inc(&stp->st_stid.sc_count); stp->st_stid.sc_type = NFS4_LOCK_STID; - stp->st_stateowner = &lo->lo_owner; - atomic_inc(&lo->lo_owner.so_count); + stp->st_stateowner = nfs4_get_stateowner(&lo->lo_owner); get_nfs4_file(fp); stp->st_stid.sc_file = fp; stp->st_stid.sc_free = nfs4_free_lock_stateid; @@ -5546,7 +5546,7 @@ nfsd4_release_lockowner(struct svc_rqst *rqstp, } } - atomic_inc(&sop->so_count); + nfs4_get_stateowner(sop); break; } spin_unlock(&clp->cl_lock); -- cgit v1.2.3 From aef9583b234a4ecdbcaf2c3024f29d4244b18e83 Mon Sep 17 00:00:00 2001 From: Kinglong Mee Date: Fri, 22 Aug 2014 10:18:44 -0400 Subject: NFSD: Get reference of lockowner when coping file_lock v5: using nfs4_get_stateowner() instead of an inline function v3: Update based on Jeff's comments v2: Fix bad using of struct file_lock_operations for handle the owner Acked-by: Jeff Layton Signed-off-by: Kinglong Mee Signed-off-by: Jeff Layton --- fs/nfsd/nfs4state.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) (limited to 'fs/nfsd/nfs4state.c') diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 2d03a4188671..a91e521622c3 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -4876,9 +4876,25 @@ nfs4_transform_lock_offset(struct file_lock *lock) lock->fl_end = OFFSET_MAX; } -/* Hack!: For now, we're defining this just so we can use a pointer to it - * as a unique cookie to identify our (NFSv4's) posix locks. */ +static void nfsd4_fl_get_owner(struct file_lock *dst, struct file_lock *src) +{ + struct nfs4_lockowner *lo = (struct nfs4_lockowner *)src->fl_owner; + dst->fl_owner = (fl_owner_t)lockowner(nfs4_get_stateowner(&lo->lo_owner)); +} + +static void nfsd4_fl_put_owner(struct file_lock *fl) +{ + struct nfs4_lockowner *lo = (struct nfs4_lockowner *)fl->fl_owner; + + if (lo) { + nfs4_put_stateowner(&lo->lo_owner); + fl->fl_owner = NULL; + } +} + static const struct lock_manager_operations nfsd_posix_mng_ops = { + .lm_get_owner = nfsd4_fl_get_owner, + .lm_put_owner = nfsd4_fl_put_owner, }; static inline void @@ -5243,7 +5259,8 @@ nfsd4_lock(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, status = nfserr_openmode; goto out; } - file_lock->fl_owner = (fl_owner_t)lock_sop; + + file_lock->fl_owner = (fl_owner_t)lockowner(nfs4_get_stateowner(&lock_sop->lo_owner)); file_lock->fl_pid = current->tgid; file_lock->fl_file = filp; file_lock->fl_flags = FL_POSIX; @@ -5439,7 +5456,7 @@ nfsd4_locku(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate, } file_lock->fl_type = F_UNLCK; - file_lock->fl_owner = (fl_owner_t)lockowner(stp->st_stateowner); + file_lock->fl_owner = (fl_owner_t)lockowner(nfs4_get_stateowner(stp->st_stateowner)); file_lock->fl_pid = current->tgid; file_lock->fl_file = filp; file_lock->fl_flags = FL_POSIX; -- cgit v1.2.3 From 415b96c5a1fe31ed9deb0618e95ecbb1df3de54c Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 22 Aug 2014 12:26:36 -0400 Subject: nfsd: fix potential lease memory leak in nfs4_setlease It's unlikely to ever occur, but if there were already a lease set on the file then we could end up getting back a different pointer on a successful setlease attempt than the one we allocated. If that happens, the one we allocated could leak. In practice, I don't think this will happen due to the fact that we only try to set up the lease once per nfs4_file, but this error handling is a bit more correct given the current lease API. Cc: J. Bruce Fields Signed-off-by: Jeff Layton Reviewed-by: Christoph Hellwig --- fs/nfsd/nfs4state.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'fs/nfsd/nfs4state.c') diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index a91e521622c3..5bb4952faf5b 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -3781,7 +3781,7 @@ static struct file_lock *nfs4_alloc_init_lease(struct nfs4_file *fp, int flag) static int nfs4_setlease(struct nfs4_delegation *dp) { struct nfs4_file *fp = dp->dl_stid.sc_file; - struct file_lock *fl; + struct file_lock *fl, *ret; struct file *filp; int status = 0; @@ -3795,11 +3795,14 @@ static int nfs4_setlease(struct nfs4_delegation *dp) return -EBADF; } fl->fl_file = filp; - status = vfs_setlease(filp, fl->fl_type, &fl); + ret = fl; + status = vfs_setlease(filp, fl->fl_type, &ret); if (status) { locks_free_lock(fl); goto out_fput; } + if (ret != fl) + locks_free_lock(fl); spin_lock(&state_lock); spin_lock(&fp->fi_lock); /* Did the lease get broken before we took the lock? */ -- cgit v1.2.3 From 0efaa7e82f02fe69c05ad28e905f31fc86e6f08e Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 22 Aug 2014 10:18:45 -0400 Subject: locks: generic_delete_lease doesn't need a file_lock at all Ensure that it's OK to pass in a NULL file_lock double pointer on a F_UNLCK request and convert the vfs_setlease F_UNLCK callers to do just that. Finally, turn the BUG_ON in generic_setlease into a WARN_ON_ONCE with an error return. That's a problem we can handle without crashing the box if it occurs. Signed-off-by: Jeff Layton Reviewed-by: Christoph Hellwig --- fs/locks.c | 34 ++++++++++++++-------------------- fs/nfsd/nfs4state.c | 2 +- include/trace/events/filelock.h | 14 +++++++------- 3 files changed, 22 insertions(+), 28 deletions(-) (limited to 'fs/nfsd/nfs4state.c') diff --git a/fs/locks.c b/fs/locks.c index def1ac2e87bd..f79c74ef51ef 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -1637,22 +1637,23 @@ out: return error; } -static int generic_delete_lease(struct file *filp, struct file_lock **flp) +static int generic_delete_lease(struct file *filp) { + int error = -EAGAIN; struct file_lock *fl, **before; struct dentry *dentry = filp->f_path.dentry; struct inode *inode = dentry->d_inode; - trace_generic_delete_lease(inode, *flp); - for (before = &inode->i_flock; ((fl = *before) != NULL) && IS_LEASE(fl); before = &fl->fl_next) { - if (fl->fl_file != filp) - continue; - return (*flp)->fl_lmops->lm_change(before, F_UNLCK); + if (fl->fl_file == filp) + break; } - return -EAGAIN; + trace_generic_delete_lease(inode, fl); + if (fl) + error = fl->fl_lmops->lm_change(before, F_UNLCK); + return error; } /** @@ -1682,13 +1683,15 @@ int generic_setlease(struct file *filp, long arg, struct file_lock **flp) time_out_leases(inode); - BUG_ON(!(*flp)->fl_lmops->lm_break); - switch (arg) { case F_UNLCK: - return generic_delete_lease(filp, flp); + return generic_delete_lease(filp); case F_RDLCK: case F_WRLCK: + if (!(*flp)->fl_lmops->lm_break) { + WARN_ON_ONCE(1); + return -ENOLCK; + } return generic_add_lease(filp, arg, flp); default: return -EINVAL; @@ -1744,15 +1747,6 @@ int vfs_setlease(struct file *filp, long arg, struct file_lock **lease) } EXPORT_SYMBOL_GPL(vfs_setlease); -static int do_fcntl_delete_lease(struct file *filp) -{ - struct file_lock fl, *flp = &fl; - - lease_init(filp, F_UNLCK, flp); - - return vfs_setlease(filp, F_UNLCK, &flp); -} - static int do_fcntl_add_lease(unsigned int fd, struct file *filp, long arg) { struct file_lock *fl, *ret; @@ -1809,7 +1803,7 @@ out_unlock: int fcntl_setlease(unsigned int fd, struct file *filp, long arg) { if (arg == F_UNLCK) - return do_fcntl_delete_lease(filp); + return vfs_setlease(filp, F_UNLCK, NULL); return do_fcntl_add_lease(fd, filp, arg); } diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 5bb4952faf5b..89d54e505155 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -690,7 +690,7 @@ static void nfs4_put_deleg_lease(struct nfs4_file *fp) spin_unlock(&fp->fi_lock); if (filp) { - vfs_setlease(filp, F_UNLCK, &fl); + vfs_setlease(filp, F_UNLCK, NULL); fput(filp); } } diff --git a/include/trace/events/filelock.h b/include/trace/events/filelock.h index 59d11c22f076..a0d008070962 100644 --- a/include/trace/events/filelock.h +++ b/include/trace/events/filelock.h @@ -53,15 +53,15 @@ DECLARE_EVENT_CLASS(filelock_lease, ), TP_fast_assign( - __entry->fl = fl; + __entry->fl = fl ? fl : NULL; __entry->s_dev = inode->i_sb->s_dev; __entry->i_ino = inode->i_ino; - __entry->fl_next = fl->fl_next; - __entry->fl_owner = fl->fl_owner; - __entry->fl_flags = fl->fl_flags; - __entry->fl_type = fl->fl_type; - __entry->fl_break_time = fl->fl_break_time; - __entry->fl_downgrade_time = fl->fl_downgrade_time; + __entry->fl_next = fl ? fl->fl_next : NULL; + __entry->fl_owner = fl ? fl->fl_owner : NULL; + __entry->fl_flags = fl ? fl->fl_flags : 0; + __entry->fl_type = fl ? fl->fl_type : 0; + __entry->fl_break_time = fl ? fl->fl_break_time : 0; + __entry->fl_downgrade_time = fl ? fl->fl_downgrade_time : 0; ), TP_printk("fl=0x%p dev=0x%x:0x%x ino=0x%lx fl_next=0x%p fl_owner=0x%p fl_flags=%s fl_type=%s fl_break_time=%lu fl_downgrade_time=%lu", -- cgit v1.2.3 From 0c637be884f5eaa0ee53396ea7686ec0de03d126 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 22 Aug 2014 12:05:43 -0400 Subject: nfsd: don't keep a pointer to the lease in nfs4_file Now that we don't need to pass in an actual lease pointer to vfs_setlease on unlock, we can stop tracking a pointer to the lease in the nfs4_file. Switch all of the places that check the fi_lease to check fi_deleg_file instead. We always set that at the same time so it will have the same semantics. Cc: J. Bruce Fields Signed-off-by: Jeff Layton Reviewed-by: Christoph Hellwig --- fs/nfsd/nfs4state.c | 13 ++++--------- fs/nfsd/state.h | 1 - 2 files changed, 4 insertions(+), 10 deletions(-) (limited to 'fs/nfsd/nfs4state.c') diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 89d54e505155..188cd68aefb6 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -679,14 +679,10 @@ nfs4_put_stid(struct nfs4_stid *s) static void nfs4_put_deleg_lease(struct nfs4_file *fp) { struct file *filp = NULL; - struct file_lock *fl; spin_lock(&fp->fi_lock); - if (fp->fi_lease && atomic_dec_and_test(&fp->fi_delegees)) { + if (fp->fi_deleg_file && atomic_dec_and_test(&fp->fi_delegees)) swap(filp, fp->fi_deleg_file); - fl = fp->fi_lease; - fp->fi_lease = NULL; - } spin_unlock(&fp->fi_lock); if (filp) { @@ -3068,8 +3064,8 @@ static void nfsd4_init_file(struct nfs4_file *fp, struct knfsd_fh *fh) INIT_LIST_HEAD(&fp->fi_stateids); INIT_LIST_HEAD(&fp->fi_delegations); fh_copy_shallow(&fp->fi_fhandle, fh); + fp->fi_deleg_file = NULL; fp->fi_had_conflict = false; - fp->fi_lease = NULL; fp->fi_share_deny = 0; memset(fp->fi_fds, 0, sizeof(fp->fi_fds)); memset(fp->fi_access, 0, sizeof(fp->fi_access)); @@ -3810,13 +3806,12 @@ static int nfs4_setlease(struct nfs4_delegation *dp) if (fp->fi_had_conflict) goto out_unlock; /* Race breaker */ - if (fp->fi_lease) { + if (fp->fi_deleg_file) { status = 0; atomic_inc(&fp->fi_delegees); hash_delegation_locked(dp, fp); goto out_unlock; } - fp->fi_lease = fl; fp->fi_deleg_file = filp; atomic_set(&fp->fi_delegees, 1); hash_delegation_locked(dp, fp); @@ -3849,7 +3844,7 @@ nfs4_set_delegation(struct nfs4_client *clp, struct svc_fh *fh, spin_lock(&state_lock); spin_lock(&fp->fi_lock); dp->dl_stid.sc_file = fp; - if (!fp->fi_lease) { + if (!fp->fi_deleg_file) { spin_unlock(&fp->fi_lock); spin_unlock(&state_lock); status = nfs4_setlease(dp); diff --git a/fs/nfsd/state.h b/fs/nfsd/state.h index 4a89e00d7461..64f291a25a8c 100644 --- a/fs/nfsd/state.h +++ b/fs/nfsd/state.h @@ -477,7 +477,6 @@ struct nfs4_file { atomic_t fi_access[2]; u32 fi_share_deny; struct file *fi_deleg_file; - struct file_lock *fi_lease; atomic_t fi_delegees; struct knfsd_fh fi_fhandle; bool fi_had_conflict; -- cgit v1.2.3 From e6f5c78930e409f3a6b37f5484313a416359ac7f Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 22 Aug 2014 10:40:25 -0400 Subject: locks: plumb a "priv" pointer into the setlease routines In later patches, we're going to add a new lock_manager_operation to finish setting up the lease while still holding the i_lock. To do this, we'll need to pass a little bit of info in the fcntl setlease case (primarily an fasync structure). Plumb the extra pointer into there in advance of that. We declare this pointer as a void ** to make it clear that this is private info, and that the caller isn't required to set this unless the lm_setup specifically requires it. Signed-off-by: Jeff Layton Reviewed-by: Christoph Hellwig --- Documentation/filesystems/Locking | 2 +- Documentation/filesystems/vfs.txt | 2 +- fs/cifs/cifsfs.c | 7 ++++--- fs/libfs.c | 4 +++- fs/locks.c | 32 ++++++++++++++++++++------------ fs/nfsd/nfs4state.c | 4 ++-- include/linux/fs.h | 12 ++++++------ 7 files changed, 37 insertions(+), 26 deletions(-) (limited to 'fs/nfsd/nfs4state.c') diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking index f1997e9da61f..3d92049ae71d 100644 --- a/Documentation/filesystems/Locking +++ b/Documentation/filesystems/Locking @@ -464,7 +464,7 @@ prototypes: size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); - int (*setlease)(struct file *, long, struct file_lock **); + int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *, int, loff_t, loff_t); }; diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt index 61d65cc65c54..28ebd49f169f 100644 --- a/Documentation/filesystems/vfs.txt +++ b/Documentation/filesystems/vfs.txt @@ -826,7 +826,7 @@ struct file_operations { int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, struct pipe_inode_info *, size_t, unsigned int); - int (*setlease)(struct file *, long arg, struct file_lock **); + int (*setlease)(struct file *, long arg, struct file_lock **, void **); long (*fallocate)(struct file *, int mode, loff_t offset, loff_t len); int (*show_fdinfo)(struct seq_file *m, struct file *f); }; diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index ac4f260155c8..85c70d5969ac 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -800,7 +800,8 @@ static loff_t cifs_llseek(struct file *file, loff_t offset, int whence) return generic_file_llseek(file, offset, whence); } -static int cifs_setlease(struct file *file, long arg, struct file_lock **lease) +static int +cifs_setlease(struct file *file, long arg, struct file_lock **lease, void **priv) { /* * Note that this is called by vfs setlease with i_lock held to @@ -815,7 +816,7 @@ static int cifs_setlease(struct file *file, long arg, struct file_lock **lease) /* check if file is oplocked */ if (((arg == F_RDLCK) && CIFS_CACHE_READ(CIFS_I(inode))) || ((arg == F_WRLCK) && CIFS_CACHE_WRITE(CIFS_I(inode)))) - return generic_setlease(file, arg, lease); + return generic_setlease(file, arg, lease, priv); else if (tlink_tcon(cfile->tlink)->local_lease && !CIFS_CACHE_READ(CIFS_I(inode))) /* @@ -826,7 +827,7 @@ static int cifs_setlease(struct file *file, long arg, struct file_lock **lease) * knows that the file won't be changed on the server by anyone * else. */ - return generic_setlease(file, arg, lease); + return generic_setlease(file, arg, lease, priv); else return -EAGAIN; } diff --git a/fs/libfs.c b/fs/libfs.c index 29012a303ef8..171d2846f2a3 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -1081,12 +1081,14 @@ EXPORT_SYMBOL(alloc_anon_inode); * @filp: file pointer * @arg: type of lease to obtain * @flp: new lease supplied for insertion + * @priv: private data for lm_setup operation * * Generic helper for filesystems that do not wish to allow leases to be set. * All arguments are ignored and it just returns -EINVAL. */ int -simple_nosetlease(struct file *filp, long arg, struct file_lock **flp) +simple_nosetlease(struct file *filp, long arg, struct file_lock **flp, + void **priv) { return -EINVAL; } diff --git a/fs/locks.c b/fs/locks.c index e16c2c61a44f..4fa269b0bdef 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -1297,7 +1297,6 @@ int lease_modify(struct file_lock **before, int arg) } return 0; } - EXPORT_SYMBOL(lease_modify); static bool past_time(unsigned long then) @@ -1543,7 +1542,8 @@ check_conflicting_open(const struct dentry *dentry, const long arg) return ret; } -static int generic_add_lease(struct file *filp, long arg, struct file_lock **flp) +static int +generic_add_lease(struct file *filp, long arg, struct file_lock **flp, void **priv) { struct file_lock *fl, **before, **my_before = NULL, *lease; struct dentry *dentry = filp->f_path.dentry; @@ -1630,11 +1630,14 @@ static int generic_add_lease(struct file *filp, long arg, struct file_lock **flp smp_mb(); error = check_conflicting_open(dentry, arg); if (error) - locks_unlink_lock(before); + goto out_unlink; out: if (is_deleg) mutex_unlock(&inode->i_mutex); return error; +out_unlink: + locks_unlink_lock(before); + goto out; } static int generic_delete_lease(struct file *filp) @@ -1661,13 +1664,15 @@ static int generic_delete_lease(struct file *filp) * @filp: file pointer * @arg: type of lease to obtain * @flp: input - file_lock to use, output - file_lock inserted + * @priv: private data for lm_setup * * The (input) flp->fl_lmops->lm_break function is required * by break_lease(). * * Called with inode->i_lock held. */ -int generic_setlease(struct file *filp, long arg, struct file_lock **flp) +int generic_setlease(struct file *filp, long arg, struct file_lock **flp, + void **priv) { struct dentry *dentry = filp->f_path.dentry; struct inode *inode = dentry->d_inode; @@ -1692,19 +1697,20 @@ int generic_setlease(struct file *filp, long arg, struct file_lock **flp) WARN_ON_ONCE(1); return -ENOLCK; } - return generic_add_lease(filp, arg, flp); + return generic_add_lease(filp, arg, flp, priv); default: return -EINVAL; } } EXPORT_SYMBOL(generic_setlease); -static int __vfs_setlease(struct file *filp, long arg, struct file_lock **lease) +static int +__vfs_setlease(struct file *filp, long arg, struct file_lock **lease, void **priv) { if (filp->f_op->setlease) - return filp->f_op->setlease(filp, arg, lease); + return filp->f_op->setlease(filp, arg, lease, priv); else - return generic_setlease(filp, arg, lease); + return generic_setlease(filp, arg, lease, priv); } /** @@ -1712,6 +1718,7 @@ static int __vfs_setlease(struct file *filp, long arg, struct file_lock **lease) * @filp: file pointer * @arg: type of lease to obtain * @lease: file_lock to use when adding a lease + * @priv: private info for lm_setup when adding a lease * * Call this to establish a lease on the file. The "lease" argument is not * used for F_UNLCK requests and may be NULL. For commands that set or alter @@ -1720,13 +1727,14 @@ static int __vfs_setlease(struct file *filp, long arg, struct file_lock **lease) * stack trace). */ -int vfs_setlease(struct file *filp, long arg, struct file_lock **lease) +int +vfs_setlease(struct file *filp, long arg, struct file_lock **lease, void **priv) { struct inode *inode = file_inode(filp); int error; spin_lock(&inode->i_lock); - error = __vfs_setlease(filp, arg, lease); + error = __vfs_setlease(filp, arg, lease, priv); spin_unlock(&inode->i_lock); return error; @@ -1751,7 +1759,7 @@ static int do_fcntl_add_lease(unsigned int fd, struct file *filp, long arg) } ret = fl; spin_lock(&inode->i_lock); - error = __vfs_setlease(filp, arg, &ret); + error = __vfs_setlease(filp, arg, &ret, NULL); if (error) goto out_unlock; if (ret == fl) @@ -1789,7 +1797,7 @@ out_unlock: int fcntl_setlease(unsigned int fd, struct file *filp, long arg) { if (arg == F_UNLCK) - return vfs_setlease(filp, F_UNLCK, NULL); + return vfs_setlease(filp, F_UNLCK, NULL, NULL); return do_fcntl_add_lease(fd, filp, arg); } diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 188cd68aefb6..7c803db2a027 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -686,7 +686,7 @@ static void nfs4_put_deleg_lease(struct nfs4_file *fp) spin_unlock(&fp->fi_lock); if (filp) { - vfs_setlease(filp, F_UNLCK, NULL); + vfs_setlease(filp, F_UNLCK, NULL, NULL); fput(filp); } } @@ -3792,7 +3792,7 @@ static int nfs4_setlease(struct nfs4_delegation *dp) } fl->fl_file = filp; ret = fl; - status = vfs_setlease(filp, fl->fl_type, &ret); + status = vfs_setlease(filp, fl->fl_type, &fl, NULL); if (status) { locks_free_lock(fl); goto out_fput; diff --git a/include/linux/fs.h b/include/linux/fs.h index 96528f73dda4..f1870eb67b02 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -982,8 +982,8 @@ extern int vfs_cancel_lock(struct file *filp, struct file_lock *fl); extern int flock_lock_file_wait(struct file *filp, struct file_lock *fl); extern int __break_lease(struct inode *inode, unsigned int flags, unsigned int type); extern void lease_get_mtime(struct inode *, struct timespec *time); -extern int generic_setlease(struct file *, long, struct file_lock **); -extern int vfs_setlease(struct file *, long, struct file_lock **); +extern int generic_setlease(struct file *, long, struct file_lock **, void **priv); +extern int vfs_setlease(struct file *, long, struct file_lock **, void **); extern int lease_modify(struct file_lock **, int); #else /* !CONFIG_FILE_LOCKING */ static inline int fcntl_getlk(struct file *file, unsigned int cmd, @@ -1100,13 +1100,13 @@ static inline void lease_get_mtime(struct inode *inode, struct timespec *time) } static inline int generic_setlease(struct file *filp, long arg, - struct file_lock **flp) + struct file_lock **flp, void **priv) { return -EINVAL; } static inline int vfs_setlease(struct file *filp, long arg, - struct file_lock **lease) + struct file_lock **lease, void **priv) { return -EINVAL; } @@ -1494,7 +1494,7 @@ struct file_operations { int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); - int (*setlease)(struct file *, long, struct file_lock **); + int (*setlease)(struct file *, long, struct file_lock **, void **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); int (*show_fdinfo)(struct seq_file *m, struct file *f); @@ -2599,7 +2599,7 @@ extern int simple_write_end(struct file *file, struct address_space *mapping, struct page *page, void *fsdata); extern int always_delete_dentry(const struct dentry *); extern struct inode *alloc_anon_inode(struct super_block *); -extern int simple_nosetlease(struct file *, long, struct file_lock **); +extern int simple_nosetlease(struct file *, long, struct file_lock **, void **); extern const struct dentry_operations simple_dentry_operations; extern struct dentry *simple_lookup(struct inode *, struct dentry *, unsigned int flags); -- cgit v1.2.3 From 1c7dd2ff430fa14b45c9def54468e3a25ab8342b Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Fri, 22 Aug 2014 10:55:47 -0400 Subject: locks: define a lm_setup handler for leases ...and move the fasync setup into it for fcntl lease calls. At the same time, change the semantics of how the file_lock double-pointer is handled. Up until now, on a successful lease return you got a pointer to the lock on the list. This is bad, since that pointer can no longer be relied on as valid once the inode->i_lock has been released. Change the code to instead just zero out the pointer if the lease we passed in ended up being used. Then the callers can just check to see if it's NULL after the call and free it if it isn't. The priv argument has the same semantics. The lm_setup function can zero the pointer out to signal to the caller that it should not be freed after the function returns. Signed-off-by: Jeff Layton Reviewed-by: Christoph Hellwig --- fs/locks.c | 92 ++++++++++++++++++++++++++++------------------------- fs/nfsd/nfs4state.c | 6 ++-- include/linux/fs.h | 1 + 3 files changed, 51 insertions(+), 48 deletions(-) (limited to 'fs/nfsd/nfs4state.c') diff --git a/fs/locks.c b/fs/locks.c index 4fa269b0bdef..a237ba632e8d 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -432,9 +432,27 @@ static void lease_break_callback(struct file_lock *fl) kill_fasync(&fl->fl_fasync, SIGIO, POLL_MSG); } +static void +lease_setup(struct file_lock *fl, void **priv) +{ + struct file *filp = fl->fl_file; + struct fasync_struct *fa = *priv; + + /* + * fasync_insert_entry() returns the old entry if any. If there was no + * old entry, then it used "priv" and inserted it into the fasync list. + * Clear the pointer to indicate that it shouldn't be freed. + */ + if (!fasync_insert_entry(fa->fa_fd, filp, &fl->fl_fasync, fa)) + *priv = NULL; + + __f_setown(filp, task_pid(current), PIDTYPE_PID, 0); +} + static const struct lock_manager_operations lease_manager_ops = { .lm_break = lease_break_callback, .lm_change = lease_modify, + .lm_setup = lease_setup, }; /* @@ -1607,10 +1625,11 @@ generic_add_lease(struct file *filp, long arg, struct file_lock **flp, void **pr } if (my_before != NULL) { + lease = *my_before; error = lease->fl_lmops->lm_change(my_before, arg); - if (!error) - *flp = *my_before; - goto out; + if (error) + goto out; + goto out_setup; } error = -EINVAL; @@ -1631,9 +1650,15 @@ generic_add_lease(struct file *filp, long arg, struct file_lock **flp, void **pr error = check_conflicting_open(dentry, arg); if (error) goto out_unlink; + +out_setup: + if (lease->fl_lmops->lm_setup) + lease->fl_lmops->lm_setup(lease, priv); out: if (is_deleg) mutex_unlock(&inode->i_mutex); + if (!error && !my_before) + *flp = NULL; return error; out_unlink: locks_unlink_lock(before); @@ -1661,10 +1686,11 @@ static int generic_delete_lease(struct file *filp) /** * generic_setlease - sets a lease on an open file - * @filp: file pointer - * @arg: type of lease to obtain - * @flp: input - file_lock to use, output - file_lock inserted - * @priv: private data for lm_setup + * @filp: file pointer + * @arg: type of lease to obtain + * @flp: input - file_lock to use, output - file_lock inserted + * @priv: private data for lm_setup (may be NULL if lm_setup + * doesn't require it) * * The (input) flp->fl_lmops->lm_break function is required * by break_lease(). @@ -1704,29 +1730,23 @@ int generic_setlease(struct file *filp, long arg, struct file_lock **flp, } EXPORT_SYMBOL(generic_setlease); -static int -__vfs_setlease(struct file *filp, long arg, struct file_lock **lease, void **priv) -{ - if (filp->f_op->setlease) - return filp->f_op->setlease(filp, arg, lease, priv); - else - return generic_setlease(filp, arg, lease, priv); -} - /** * vfs_setlease - sets a lease on an open file - * @filp: file pointer - * @arg: type of lease to obtain - * @lease: file_lock to use when adding a lease - * @priv: private info for lm_setup when adding a lease + * @filp: file pointer + * @arg: type of lease to obtain + * @lease: file_lock to use when adding a lease + * @priv: private info for lm_setup when adding a lease (may be + * NULL if lm_setup doesn't require it) * * Call this to establish a lease on the file. The "lease" argument is not * used for F_UNLCK requests and may be NULL. For commands that set or alter * an existing lease, the (*lease)->fl_lmops->lm_break operation must be set; * if not, this function will return -ENOLCK (and generate a scary-looking * stack trace). + * + * The "priv" pointer is passed directly to the lm_setup function as-is. It + * may be NULL if the lm_setup operation doesn't require it. */ - int vfs_setlease(struct file *filp, long arg, struct file_lock **lease, void **priv) { @@ -1734,17 +1754,18 @@ vfs_setlease(struct file *filp, long arg, struct file_lock **lease, void **priv) int error; spin_lock(&inode->i_lock); - error = __vfs_setlease(filp, arg, lease, priv); + if (filp->f_op->setlease) + error = filp->f_op->setlease(filp, arg, lease, priv); + else + error = generic_setlease(filp, arg, lease, priv); spin_unlock(&inode->i_lock); - return error; } EXPORT_SYMBOL_GPL(vfs_setlease); static int do_fcntl_add_lease(unsigned int fd, struct file *filp, long arg) { - struct file_lock *fl, *ret; - struct inode *inode = file_inode(filp); + struct file_lock *fl; struct fasync_struct *new; int error; @@ -1757,26 +1778,9 @@ static int do_fcntl_add_lease(unsigned int fd, struct file *filp, long arg) locks_free_lock(fl); return -ENOMEM; } - ret = fl; - spin_lock(&inode->i_lock); - error = __vfs_setlease(filp, arg, &ret, NULL); - if (error) - goto out_unlock; - if (ret == fl) - fl = NULL; + new->fa_fd = fd; - /* - * fasync_insert_entry() returns the old entry if any. - * If there was no old entry, then it used 'new' and - * inserted it into the fasync list. Clear new so that - * we don't release it here. - */ - if (!fasync_insert_entry(fd, filp, &ret->fl_fasync, new)) - new = NULL; - - __f_setown(filp, task_pid(current), PIDTYPE_PID, 0); -out_unlock: - spin_unlock(&inode->i_lock); + error = vfs_setlease(filp, arg, &fl, (void **)&new); if (fl) locks_free_lock(fl); if (new) diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 7c803db2a027..5349528136e2 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -3793,12 +3793,10 @@ static int nfs4_setlease(struct nfs4_delegation *dp) fl->fl_file = filp; ret = fl; status = vfs_setlease(filp, fl->fl_type, &fl, NULL); - if (status) { + if (fl) locks_free_lock(fl); + if (status) goto out_fput; - } - if (ret != fl) - locks_free_lock(fl); spin_lock(&state_lock); spin_lock(&fp->fi_lock); /* Did the lease get broken before we took the lock? */ diff --git a/include/linux/fs.h b/include/linux/fs.h index f1870eb67b02..9a6d56154dd5 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -874,6 +874,7 @@ struct lock_manager_operations { int (*lm_grant)(struct file_lock *, int); void (*lm_break)(struct file_lock *); int (*lm_change)(struct file_lock **, int); + void (*lm_setup)(struct file_lock *, void **); }; struct lock_manager { -- cgit v1.2.3 From c45198eda2794bb72601c9f96266d8b95db66dd5 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Mon, 1 Sep 2014 07:12:07 -0400 Subject: locks: move freeing of leases outside of i_lock There was only one place where we still could free a file_lock while holding the i_lock -- lease_modify. Add a new list_head argument to the lm_change operation, pass in a private list when calling it, and fix those callers to dispose of the list once the lock has been dropped. Signed-off-by: Jeff Layton Reviewed-by: Christoph Hellwig --- Documentation/filesystems/Locking | 3 +-- fs/locks.c | 34 ++++++++++++++++++++++------------ fs/nfsd/nfs4state.c | 6 +++--- include/linux/fs.h | 7 ++++--- 4 files changed, 30 insertions(+), 20 deletions(-) (limited to 'fs/nfsd/nfs4state.c') diff --git a/Documentation/filesystems/Locking b/Documentation/filesystems/Locking index 4af288e38f13..94d93b1f8b53 100644 --- a/Documentation/filesystems/Locking +++ b/Documentation/filesystems/Locking @@ -469,8 +469,7 @@ prototypes: }; locking rules: - All may block except for ->setlease. - No VFS locks held on entry except for ->setlease. + All may block. ->llseek() locking has moved from llseek to the individual llseek implementations. If your fs is not using generic_file_llseek, you diff --git a/fs/locks.c b/fs/locks.c index eb463257f867..c0f789dfa655 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -1292,7 +1292,7 @@ static void lease_clear_pending(struct file_lock *fl, int arg) } /* We already had a lease on this file; just change its type */ -int lease_modify(struct file_lock **before, int arg) +int lease_modify(struct file_lock **before, int arg, struct list_head *dispose) { struct file_lock *fl = *before; int error = assign_type(fl, arg); @@ -1311,7 +1311,7 @@ int lease_modify(struct file_lock **before, int arg) printk(KERN_ERR "locks_delete_lock: fasync == %p\n", fl->fl_fasync); fl->fl_fasync = NULL; } - locks_delete_lock(before, NULL); + locks_delete_lock(before, dispose); } return 0; } @@ -1325,7 +1325,7 @@ static bool past_time(unsigned long then) return time_after(jiffies, then); } -static void time_out_leases(struct inode *inode) +static void time_out_leases(struct inode *inode, struct list_head *dispose) { struct file_lock **before; struct file_lock *fl; @@ -1336,9 +1336,9 @@ static void time_out_leases(struct inode *inode) while ((fl = *before) && IS_LEASE(fl) && lease_breaking(fl)) { trace_time_out_leases(inode, fl); if (past_time(fl->fl_downgrade_time)) - lease_modify(before, F_RDLCK); + lease_modify(before, F_RDLCK, dispose); if (past_time(fl->fl_break_time)) - lease_modify(before, F_UNLCK); + lease_modify(before, F_UNLCK, dispose); if (fl == *before) /* lease_modify may have freed fl */ before = &fl->fl_next; } @@ -1373,6 +1373,7 @@ int __break_lease(struct inode *inode, unsigned int mode, unsigned int type) int i_have_this_lease = 0; bool lease_conflict = false; int want_write = (mode & O_ACCMODE) != O_RDONLY; + LIST_HEAD(dispose); new_fl = lease_alloc(NULL, want_write ? F_WRLCK : F_RDLCK); if (IS_ERR(new_fl)) @@ -1381,7 +1382,7 @@ int __break_lease(struct inode *inode, unsigned int mode, unsigned int type) spin_lock(&inode->i_lock); - time_out_leases(inode); + time_out_leases(inode, &dispose); flock = inode->i_flock; if ((flock == NULL) || !IS_LEASE(flock)) @@ -1436,6 +1437,7 @@ restart: locks_insert_block(flock, new_fl); trace_break_lease_block(inode, new_fl); spin_unlock(&inode->i_lock); + locks_dispose_list(&dispose); error = wait_event_interruptible_timeout(new_fl->fl_wait, !new_fl->fl_next, break_time); spin_lock(&inode->i_lock); @@ -1443,7 +1445,7 @@ restart: locks_delete_block(new_fl); if (error >= 0) { if (error == 0) - time_out_leases(inode); + time_out_leases(inode, &dispose); /* * Wait for the next conflicting lease that has not been * broken yet @@ -1458,6 +1460,7 @@ restart: out: spin_unlock(&inode->i_lock); + locks_dispose_list(&dispose); locks_free_lock(new_fl); return error; } @@ -1522,9 +1525,10 @@ int fcntl_getlease(struct file *filp) struct file_lock *fl; struct inode *inode = file_inode(filp); int type = F_UNLCK; + LIST_HEAD(dispose); spin_lock(&inode->i_lock); - time_out_leases(file_inode(filp)); + time_out_leases(file_inode(filp), &dispose); for (fl = file_inode(filp)->i_flock; fl && IS_LEASE(fl); fl = fl->fl_next) { if (fl->fl_file == filp) { @@ -1533,6 +1537,7 @@ int fcntl_getlease(struct file *filp) } } spin_unlock(&inode->i_lock); + locks_dispose_list(&dispose); return type; } @@ -1570,6 +1575,7 @@ generic_add_lease(struct file *filp, long arg, struct file_lock **flp, void **pr struct inode *inode = dentry->d_inode; bool is_deleg = (*flp)->fl_flags & FL_DELEG; int error; + LIST_HEAD(dispose); lease = *flp; trace_generic_add_lease(inode, lease); @@ -1593,7 +1599,7 @@ generic_add_lease(struct file *filp, long arg, struct file_lock **flp, void **pr } spin_lock(&inode->i_lock); - time_out_leases(inode); + time_out_leases(inode, &dispose); error = check_conflicting_open(dentry, arg); if (error) goto out; @@ -1630,7 +1636,7 @@ generic_add_lease(struct file *filp, long arg, struct file_lock **flp, void **pr if (my_before != NULL) { lease = *my_before; - error = lease->fl_lmops->lm_change(my_before, arg); + error = lease->fl_lmops->lm_change(my_before, arg, &dispose); if (error) goto out; goto out_setup; @@ -1660,6 +1666,7 @@ out_setup: lease->fl_lmops->lm_setup(lease, priv); out: spin_unlock(&inode->i_lock); + locks_dispose_list(&dispose); if (is_deleg) mutex_unlock(&inode->i_mutex); if (!error && !my_before) @@ -1676,8 +1683,10 @@ static int generic_delete_lease(struct file *filp) struct file_lock *fl, **before; struct dentry *dentry = filp->f_path.dentry; struct inode *inode = dentry->d_inode; + LIST_HEAD(dispose); spin_lock(&inode->i_lock); + time_out_leases(inode, &dispose); for (before = &inode->i_flock; ((fl = *before) != NULL) && IS_LEASE(fl); before = &fl->fl_next) { @@ -1686,8 +1695,9 @@ static int generic_delete_lease(struct file *filp) } trace_generic_delete_lease(inode, fl); if (fl) - error = fl->fl_lmops->lm_change(before, F_UNLCK); + error = fl->fl_lmops->lm_change(before, F_UNLCK, &dispose); spin_unlock(&inode->i_lock); + locks_dispose_list(&dispose); return error; } @@ -2372,7 +2382,7 @@ void locks_remove_file(struct file *filp) while ((fl = *before) != NULL) { if (fl->fl_file == filp) { if (IS_LEASE(fl)) { - lease_modify(before, F_UNLCK); + lease_modify(before, F_UNLCK, &dispose); continue; } diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 5349528136e2..604ab6decd28 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -3427,11 +3427,11 @@ static void nfsd_break_deleg_cb(struct file_lock *fl) spin_unlock(&fp->fi_lock); } -static -int nfsd_change_deleg_cb(struct file_lock **onlist, int arg) +static int +nfsd_change_deleg_cb(struct file_lock **onlist, int arg, struct list_head *dispose) { if (arg & F_UNLCK) - return lease_modify(onlist, arg); + return lease_modify(onlist, arg, dispose); else return -EAGAIN; } diff --git a/include/linux/fs.h b/include/linux/fs.h index 9a6d56154dd5..f419f718e447 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -873,7 +873,7 @@ struct lock_manager_operations { void (*lm_notify)(struct file_lock *); /* unblock callback */ int (*lm_grant)(struct file_lock *, int); void (*lm_break)(struct file_lock *); - int (*lm_change)(struct file_lock **, int); + int (*lm_change)(struct file_lock **, int, struct list_head *); void (*lm_setup)(struct file_lock *, void **); }; @@ -985,7 +985,7 @@ extern int __break_lease(struct inode *inode, unsigned int flags, unsigned int t extern void lease_get_mtime(struct inode *, struct timespec *time); extern int generic_setlease(struct file *, long, struct file_lock **, void **priv); extern int vfs_setlease(struct file *, long, struct file_lock **, void **); -extern int lease_modify(struct file_lock **, int); +extern int lease_modify(struct file_lock **, int, struct list_head *); #else /* !CONFIG_FILE_LOCKING */ static inline int fcntl_getlk(struct file *file, unsigned int cmd, struct flock __user *user) @@ -1112,7 +1112,8 @@ static inline int vfs_setlease(struct file *filp, long arg, return -EINVAL; } -static inline int lease_modify(struct file_lock **before, int arg) +static inline int lease_modify(struct file_lock **before, int arg, + struct list_head *dispose) { return -EINVAL; } -- cgit v1.2.3 From 4d01b7f5e7576858b71cbaa72b541e17a229cb91 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Mon, 1 Sep 2014 15:06:54 -0400 Subject: locks: give lm_break a return value Christoph suggests: "Add a return value to lm_break so that the lock manager can tell the core code "you can delete this lease right now". That gets rid of the games with the timeout which require all kinds of race avoidance code in the users." Do that here and have the nfsd lease break routine use it when it detects that there was a race between setting up the lease and it being broken. Signed-off-by: Jeff Layton Reviewed-by: Christoph Hellwig --- fs/locks.c | 17 +++++++++++++---- fs/nfsd/nfs4state.c | 17 +++++++++-------- include/linux/fs.h | 2 +- 3 files changed, 23 insertions(+), 13 deletions(-) (limited to 'fs/nfsd/nfs4state.c') diff --git a/fs/locks.c b/fs/locks.c index 7d627ac0ed87..aed4a957d232 100644 --- a/fs/locks.c +++ b/fs/locks.c @@ -427,9 +427,11 @@ static int flock_to_posix_lock(struct file *filp, struct file_lock *fl, } /* default lease lock manager operations */ -static void lease_break_callback(struct file_lock *fl) +static bool +lease_break_callback(struct file_lock *fl) { kill_fasync(&fl->fl_fasync, SIGIO, POLL_MSG); + return false; } static void @@ -1382,7 +1384,7 @@ int __break_lease(struct inode *inode, unsigned int mode, unsigned int type) { int error = 0; struct file_lock *new_fl; - struct file_lock *fl; + struct file_lock *fl, **before; unsigned long break_time; int want_write = (mode & O_ACCMODE) != O_RDONLY; LIST_HEAD(dispose); @@ -1406,7 +1408,9 @@ int __break_lease(struct inode *inode, unsigned int mode, unsigned int type) break_time++; /* so that 0 means no break time */ } - for (fl = inode->i_flock; fl && IS_LEASE(fl); fl = fl->fl_next) { + for (before = &inode->i_flock; + ((fl = *before) != NULL) && IS_LEASE(fl); + before = &fl->fl_next) { if (!leases_conflict(fl, new_fl)) continue; if (want_write) { @@ -1420,9 +1424,14 @@ int __break_lease(struct inode *inode, unsigned int mode, unsigned int type) fl->fl_flags |= FL_DOWNGRADE_PENDING; fl->fl_downgrade_time = break_time; } - fl->fl_lmops->lm_break(fl); + if (fl->fl_lmops->lm_break(fl)) + locks_delete_lock(before, &dispose); } + fl = inode->i_flock; + if (!fl || !IS_LEASE(fl)) + goto out; + if (mode & O_NONBLOCK) { trace_break_lease_noblock(inode, new_fl); error = -EWOULDBLOCK; diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c index 604ab6decd28..d1b851548b7a 100644 --- a/fs/nfsd/nfs4state.c +++ b/fs/nfsd/nfs4state.c @@ -3391,18 +3391,20 @@ static void nfsd_break_one_deleg(struct nfs4_delegation *dp) } /* Called from break_lease() with i_lock held. */ -static void nfsd_break_deleg_cb(struct file_lock *fl) +static bool +nfsd_break_deleg_cb(struct file_lock *fl) { + bool ret = false; struct nfs4_file *fp = (struct nfs4_file *)fl->fl_owner; struct nfs4_delegation *dp; if (!fp) { WARN(1, "(%p)->fl_owner NULL\n", fl); - return; + return ret; } if (fp->fi_had_conflict) { WARN(1, "duplicate break on %p\n", fp); - return; + return ret; } /* * We don't want the locks code to timeout the lease for us; @@ -3414,17 +3416,16 @@ static void nfsd_break_deleg_cb(struct file_lock *fl) spin_lock(&fp->fi_lock); fp->fi_had_conflict = true; /* - * If there are no delegations on the list, then we can't count on this - * lease ever being cleaned up. Set the fl_break_time to jiffies so that - * time_out_leases will do it ASAP. The fact that fi_had_conflict is now - * true should keep any new delegations from being hashed. + * If there are no delegations on the list, then return true + * so that the lease code will go ahead and delete it. */ if (list_empty(&fp->fi_delegations)) - fl->fl_break_time = jiffies; + ret = true; else list_for_each_entry(dp, &fp->fi_delegations, dl_perfile) nfsd_break_one_deleg(dp); spin_unlock(&fp->fi_lock); + return ret; } static int diff --git a/include/linux/fs.h b/include/linux/fs.h index f419f718e447..ed4e1897099c 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -872,7 +872,7 @@ struct lock_manager_operations { void (*lm_put_owner)(struct file_lock *); void (*lm_notify)(struct file_lock *); /* unblock callback */ int (*lm_grant)(struct file_lock *, int); - void (*lm_break)(struct file_lock *); + bool (*lm_break)(struct file_lock *); int (*lm_change)(struct file_lock **, int, struct list_head *); void (*lm_setup)(struct file_lock *, void **); }; -- cgit v1.2.3