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 <jlayton@primarydata.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
This commit is contained in:
Jeff Layton 2014-08-22 10:55:47 -04:00
parent e6f5c78930
commit 1c7dd2ff43
3 changed files with 51 additions and 48 deletions

View File

@ -432,9 +432,27 @@ static void lease_break_callback(struct file_lock *fl)
kill_fasync(&fl->fl_fasync, SIGIO, POLL_MSG); 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 = { static const struct lock_manager_operations lease_manager_ops = {
.lm_break = lease_break_callback, .lm_break = lease_break_callback,
.lm_change = lease_modify, .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) { 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);
if (!error) if (error)
*flp = *my_before; goto out;
goto out; goto out_setup;
} }
error = -EINVAL; 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); error = check_conflicting_open(dentry, arg);
if (error) if (error)
goto out_unlink; goto out_unlink;
out_setup:
if (lease->fl_lmops->lm_setup)
lease->fl_lmops->lm_setup(lease, priv);
out: out:
if (is_deleg) if (is_deleg)
mutex_unlock(&inode->i_mutex); mutex_unlock(&inode->i_mutex);
if (!error && !my_before)
*flp = NULL;
return error; return error;
out_unlink: out_unlink:
locks_unlink_lock(before); 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 * generic_setlease - sets a lease on an open file
* @filp: file pointer * @filp: file pointer
* @arg: type of lease to obtain * @arg: type of lease to obtain
* @flp: input - file_lock to use, output - file_lock inserted * @flp: input - file_lock to use, output - file_lock inserted
* @priv: private data for lm_setup * @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 * The (input) flp->fl_lmops->lm_break function is required
* by break_lease(). * by break_lease().
@ -1704,29 +1730,23 @@ int generic_setlease(struct file *filp, long arg, struct file_lock **flp,
} }
EXPORT_SYMBOL(generic_setlease); 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 * vfs_setlease - sets a lease on an open file
* @filp: file pointer * @filp: file pointer
* @arg: type of lease to obtain * @arg: type of lease to obtain
* @lease: file_lock to use when adding a lease * @lease: file_lock to use when adding a lease
* @priv: private info for lm_setup 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 * 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 * 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; * 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 * if not, this function will return -ENOLCK (and generate a scary-looking
* stack trace). * 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 int
vfs_setlease(struct file *filp, long arg, struct file_lock **lease, void **priv) 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; int error;
spin_lock(&inode->i_lock); 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); spin_unlock(&inode->i_lock);
return error; return error;
} }
EXPORT_SYMBOL_GPL(vfs_setlease); EXPORT_SYMBOL_GPL(vfs_setlease);
static int do_fcntl_add_lease(unsigned int fd, struct file *filp, long arg) static int do_fcntl_add_lease(unsigned int fd, struct file *filp, long arg)
{ {
struct file_lock *fl, *ret; struct file_lock *fl;
struct inode *inode = file_inode(filp);
struct fasync_struct *new; struct fasync_struct *new;
int error; int error;
@ -1757,26 +1778,9 @@ static int do_fcntl_add_lease(unsigned int fd, struct file *filp, long arg)
locks_free_lock(fl); locks_free_lock(fl);
return -ENOMEM; return -ENOMEM;
} }
ret = fl; new->fa_fd = fd;
spin_lock(&inode->i_lock);
error = __vfs_setlease(filp, arg, &ret, NULL);
if (error)
goto out_unlock;
if (ret == fl)
fl = NULL;
/* error = vfs_setlease(filp, arg, &fl, (void **)&new);
* 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);
if (fl) if (fl)
locks_free_lock(fl); locks_free_lock(fl);
if (new) if (new)

View File

@ -3793,12 +3793,10 @@ static int nfs4_setlease(struct nfs4_delegation *dp)
fl->fl_file = filp; fl->fl_file = filp;
ret = fl; ret = fl;
status = vfs_setlease(filp, fl->fl_type, &fl, NULL); status = vfs_setlease(filp, fl->fl_type, &fl, NULL);
if (status) { if (fl)
locks_free_lock(fl); locks_free_lock(fl);
if (status)
goto out_fput; goto out_fput;
}
if (ret != fl)
locks_free_lock(fl);
spin_lock(&state_lock); spin_lock(&state_lock);
spin_lock(&fp->fi_lock); spin_lock(&fp->fi_lock);
/* Did the lease get broken before we took the lock? */ /* Did the lease get broken before we took the lock? */

View File

@ -874,6 +874,7 @@ struct lock_manager_operations {
int (*lm_grant)(struct file_lock *, int); int (*lm_grant)(struct file_lock *, int);
void (*lm_break)(struct file_lock *); void (*lm_break)(struct file_lock *);
int (*lm_change)(struct file_lock **, int); int (*lm_change)(struct file_lock **, int);
void (*lm_setup)(struct file_lock *, void **);
}; };
struct lock_manager { struct lock_manager {