Commit 23b9a9a3 authored by Stephen Rothwell's avatar Stephen Rothwell Committed by Linus Torvalds

[PATCH] make file leases work as they should

This patch fixes the following problems in the file lease:
	when there are multiple shared leases on a file, all the
		lease holders get notified when someone opens the
		file for writing (used to be only the first).
	when a nonblocking open breaks a lease, it will time out
		as it should (used to never time out).

This should make the leases code more usable (hopefully).
parent 9d33a271
...@@ -119,6 +119,7 @@ ...@@ -119,6 +119,7 @@
#include <linux/smp_lock.h> #include <linux/smp_lock.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/capability.h> #include <linux/capability.h>
#include <linux/timer.h>
#include <linux/time.h> #include <linux/time.h>
#include <linux/fs.h> #include <linux/fs.h>
...@@ -1020,6 +1021,46 @@ int posix_lock_file(struct file *filp, struct file_lock *caller, ...@@ -1020,6 +1021,46 @@ int posix_lock_file(struct file *filp, struct file_lock *caller,
return error; return error;
} }
/* We already had a lease on this file; just change its type */
static int lease_modify(struct file_lock **before, int arg)
{
struct file_lock *fl = *before;
int error = assign_type(fl, arg);
if (error)
return error;
locks_wake_up_blocks(fl);
if (arg == F_UNLCK) {
struct file *filp = fl->fl_file;
filp->f_owner.pid = 0;
filp->f_owner.uid = 0;
filp->f_owner.euid = 0;
filp->f_owner.signum = 0;
locks_delete_lock(before);
}
return 0;
}
static void time_out_leases(struct inode *inode)
{
struct file_lock **before;
struct file_lock *fl;
before = &inode->i_flock;
while ((fl = *before) && IS_LEASE(fl) && (fl->fl_type & F_INPROGRESS)) {
if ((fl->fl_break_time == 0)
|| time_before(jiffies, fl->fl_break_time)) {
before = &fl->fl_next;
continue;
}
printk(KERN_INFO "lease broken - owner pid = %d\n", fl->fl_pid);
lease_modify(before, fl->fl_type & ~F_INPROGRESS);
if (fl == *before) /* lease_modify may have freed fl */
before = &fl->fl_next;
}
}
/** /**
* __get_lease - revoke all outstanding leases on file * __get_lease - revoke all outstanding leases on file
* @inode: the inode of the file to return * @inode: the inode of the file to return
...@@ -1036,34 +1077,30 @@ int __get_lease(struct inode *inode, unsigned int mode) ...@@ -1036,34 +1077,30 @@ int __get_lease(struct inode *inode, unsigned int mode)
struct file_lock *new_fl, *flock; struct file_lock *new_fl, *flock;
struct file_lock *fl; struct file_lock *fl;
int alloc_err; int alloc_err;
unsigned long break_time;
int i_have_this_lease = 0;
alloc_err = lease_alloc(NULL, 0, &new_fl); alloc_err = lease_alloc(NULL, mode & FMODE_WRITE ? F_WRLCK : F_RDLCK,
&new_fl);
lock_kernel(); lock_kernel();
time_out_leases(inode);
flock = inode->i_flock; flock = inode->i_flock;
if (flock->fl_type & F_INPROGRESS) { if ((flock == NULL) || !IS_LEASE(flock))
if ((mode & O_NONBLOCK) goto out;
|| (flock->fl_owner == current->files)) {
error = -EWOULDBLOCK; for (fl = flock; fl && IS_LEASE(fl); fl = fl->fl_next)
goto out; if (fl->fl_owner == current->files)
} i_have_this_lease = 1;
if (alloc_err != 0) {
error = alloc_err;
goto out;
}
do {
error = locks_block_on(flock, new_fl);
if (error != 0)
goto out;
flock = inode->i_flock;
if (!(flock && IS_LEASE(flock)))
goto out;
} while (flock->fl_type & F_INPROGRESS);
}
if (mode & FMODE_WRITE) { if (mode & FMODE_WRITE) {
/* If we want write access, we have to revoke any lease. */ /* If we want write access, we have to revoke any lease. */
future = F_UNLCK | F_INPROGRESS; future = F_UNLCK | F_INPROGRESS;
} else if (flock->fl_type & F_INPROGRESS) {
/* If the lease is already being broken, we just leave it */
future = flock->fl_type;
} else if (flock->fl_type & F_WRLCK) { } else if (flock->fl_type & F_WRLCK) {
/* Downgrade the exclusive lease to a read-only lease. */ /* Downgrade the exclusive lease to a read-only lease. */
future = F_RDLCK | F_INPROGRESS; future = F_RDLCK | F_INPROGRESS;
...@@ -1072,38 +1109,48 @@ int __get_lease(struct inode *inode, unsigned int mode) ...@@ -1072,38 +1109,48 @@ int __get_lease(struct inode *inode, unsigned int mode)
goto out; goto out;
} }
if (alloc_err && (flock->fl_owner != current->files)) { if (alloc_err && !i_have_this_lease && ((mode & O_NONBLOCK) == 0)) {
error = alloc_err; error = alloc_err;
goto out; goto out;
} }
fl = flock; break_time = 0;
do { if (lease_break_time > 0) {
fl->fl_type = future; break_time = jiffies + lease_break_time * HZ;
fl = fl->fl_next; if (break_time == 0)
} while (fl != NULL && IS_LEASE(fl)); break_time++; /* so that 0 means no break time */
}
kill_fasync(&flock->fl_fasync, SIGIO, POLL_MSG); for (fl = flock; fl && IS_LEASE(fl); fl = fl->fl_next) {
if (fl->fl_type != future) {
fl->fl_type = future;
fl->fl_break_time = break_time;
kill_fasync(&fl->fl_fasync, SIGIO, POLL_MSG);
}
}
if ((mode & O_NONBLOCK) || (flock->fl_owner == current->files)) { if (i_have_this_lease || (mode & O_NONBLOCK)) {
error = -EWOULDBLOCK; error = -EWOULDBLOCK;
goto out; goto out;
} }
if (lease_break_time > 0)
error = lease_break_time * HZ;
else
error = 0;
restart: restart:
error = locks_block_on_timeout(flock, new_fl, error); break_time = flock->fl_break_time;
if (error == 0) { if (break_time != 0) {
/* We timed out. Unilaterally break the lease. */ break_time -= jiffies;
locks_delete_lock(&inode->i_flock); if (break_time == 0)
printk(KERN_WARNING "lease timed out\n"); break_time++;
} else if (error > 0) { }
flock = inode->i_flock; error = locks_block_on_timeout(flock, new_fl, break_time);
if (flock && IS_LEASE(flock)) if (error >= 0) {
goto restart; if (error == 0)
time_out_leases(inode);
/* Wait for the next lease that has not been broken yet */
for (flock = inode->i_flock; flock && IS_LEASE(flock);
flock = flock->fl_next) {
if (flock->fl_type & F_INPROGRESS)
goto restart;
}
error = 0; error = 0;
} }
...@@ -1135,45 +1182,40 @@ time_t lease_get_mtime(struct inode *inode) ...@@ -1135,45 +1182,40 @@ time_t lease_get_mtime(struct inode *inode)
* @filp: the file * @filp: the file
* *
* The value returned by this function will be one of * The value returned by this function will be one of
* (if no lease break is pending):
* *
* %F_RDLCK to indicate a read-only (type II) lease is held. * %F_RDLCK to indicate a shared lease is held.
* *
* %F_WRLCK to indicate an exclusive lease is held. * %F_WRLCK to indicate an exclusive lease is held.
* *
* XXX: sfr & i disagree over whether F_INPROGRESS * %F_UNLCK to indicate no lease is held.
*
* (if a lease break is pending):
*
* %F_RDLCK to indicate an exclusive lease needs to be
* changed to a shared lease (or removed).
*
* %F_UNLCK to indicate the lease needs to be removed.
*
* XXX: sfr & willy disagree over whether F_INPROGRESS
* should be returned to userspace. * should be returned to userspace.
*/ */
int fcntl_getlease(struct file *filp) int fcntl_getlease(struct file *filp)
{ {
struct file_lock *fl; struct file_lock *fl;
int type = F_UNLCK;
fl = filp->f_dentry->d_inode->i_flock;
if ((fl == NULL) || !IS_LEASE(fl))
return F_UNLCK;
return fl->fl_type & ~F_INPROGRESS;
}
/* We already had a lease on this file; just change its type */ lock_kernel();
static int lease_modify(struct file_lock **before, int arg, int fd, struct file *filp) time_out_leases(filp->f_dentry->d_inode);
{ for (fl = filp->f_dentry->d_inode->i_flock; fl && IS_LEASE(fl);
struct file_lock *fl = *before; fl = fl->fl_next) {
int error = assign_type(fl, arg); if (fl->fl_file == filp) {
if (error < 0) type = fl->fl_type & ~F_INPROGRESS;
goto out; break;
}
locks_wake_up_blocks(fl);
if (arg == F_UNLCK) {
filp->f_owner.pid = 0;
filp->f_owner.uid = 0;
filp->f_owner.euid = 0;
filp->f_owner.signum = 0;
locks_delete_lock(before);
fasync_helper(fd, filp, 0, &fl->fl_fasync);
} }
unlock_kernel();
out: return type;
return error;
} }
/** /**
...@@ -1201,50 +1243,59 @@ int fcntl_setlease(unsigned int fd, struct file *filp, long arg) ...@@ -1201,50 +1243,59 @@ int fcntl_setlease(unsigned int fd, struct file *filp, long arg)
if (!S_ISREG(inode->i_mode)) if (!S_ISREG(inode->i_mode))
return -EINVAL; return -EINVAL;
lock_kernel();
time_out_leases(inode);
/* /*
* FIXME: What about F_RDLCK and files open for writing? * FIXME: What about F_RDLCK and files open for writing?
*/ */
error = -EAGAIN;
if ((arg == F_WRLCK) if ((arg == F_WRLCK)
&& ((atomic_read(&dentry->d_count) > 1) && ((atomic_read(&dentry->d_count) > 1)
|| (atomic_read(&inode->i_count) > 1))) || (atomic_read(&inode->i_count) > 1)))
return -EAGAIN; goto out_unlock;
before = &inode->i_flock;
lock_kernel();
while ((fl = *before) != NULL) { /*
if (!IS_LEASE(fl)) * At this point, we know that if there is an exclusive
break; * lease on this file, then we hold it on this filp
* (otherwise our open of this file would have blocked).
* And if we are trying to acquire an exclusive lease,
* then the file is not open by anyone (including us)
* except for this filp.
*/
for (before = &inode->i_flock;
((fl = *before) != NULL) && IS_LEASE(fl);
before = &fl->fl_next) {
if (fl->fl_file == filp) if (fl->fl_file == filp)
my_before = before; my_before = before;
else if (fl->fl_type & F_WRLCK) else if (fl->fl_type == (F_INPROGRESS | F_UNLCK))
/*
* Someone is in the process of opening this
* file for writing so we may not take an
* exclusive lease on it.
*/
wrlease_count++; wrlease_count++;
else else
rdlease_count++; rdlease_count++;
before = &fl->fl_next;
} }
if ((arg == F_RDLCK && (wrlease_count > 0)) || if ((arg == F_RDLCK && (wrlease_count > 0)) ||
(arg == F_WRLCK && ((rdlease_count + wrlease_count) > 0))) { (arg == F_WRLCK && ((rdlease_count + wrlease_count) > 0)))
error = -EAGAIN;
goto out_unlock; goto out_unlock;
}
if (my_before != NULL) { if (my_before != NULL) {
error = lease_modify(my_before, arg, fd, filp); error = lease_modify(my_before, arg);
goto out_unlock; goto out_unlock;
} }
if (arg == F_UNLCK) { error = 0;
error = 0; if (arg == F_UNLCK)
goto out_unlock; goto out_unlock;
}
if (!leases_enable) { error = -EINVAL;
error = -EINVAL; if (!leases_enable)
goto out_unlock; goto out_unlock;
}
error = lease_alloc(filp, arg, &fl); error = lease_alloc(filp, arg, &fl);
if (error) if (error)
...@@ -1616,9 +1667,15 @@ void locks_remove_flock(struct file *filp) ...@@ -1616,9 +1667,15 @@ void locks_remove_flock(struct file *filp)
before = &inode->i_flock; before = &inode->i_flock;
while ((fl = *before) != NULL) { while ((fl = *before) != NULL) {
if ((IS_FLOCK(fl) || IS_LEASE(fl)) && (fl->fl_file == filp)) { if (fl->fl_file == filp) {
locks_delete_lock(before); if (IS_FLOCK(fl)) {
continue; locks_delete_lock(before);
continue;
}
if (IS_LEASE(fl)) {
lease_modify(before, F_UNLCK);
continue;
}
} }
before = &fl->fl_next; before = &fl->fl_next;
} }
...@@ -1673,7 +1730,13 @@ static void lock_get_status(char* out, struct file_lock *fl, int id, char *pfx) ...@@ -1673,7 +1730,13 @@ static void lock_get_status(char* out, struct file_lock *fl, int id, char *pfx)
out += sprintf(out, "FLOCK ADVISORY "); out += sprintf(out, "FLOCK ADVISORY ");
} }
} else if (IS_LEASE(fl)) { } else if (IS_LEASE(fl)) {
out += sprintf(out, "LEASE MANDATORY "); out += sprintf(out, "LEASE ");
if (fl->fl_type & F_INPROGRESS)
out += sprintf(out, "BREAKING ");
else if (fl->fl_file)
out += sprintf(out, "ACTIVE ");
else
out += sprintf(out, "BREAKER ");
} else { } else {
out += sprintf(out, "UNKNOWN UNKNOWN "); out += sprintf(out, "UNKNOWN UNKNOWN ");
} }
...@@ -1684,7 +1747,9 @@ static void lock_get_status(char* out, struct file_lock *fl, int id, char *pfx) ...@@ -1684,7 +1747,9 @@ static void lock_get_status(char* out, struct file_lock *fl, int id, char *pfx)
: (fl->fl_type & LOCK_WRITE) ? "WRITE" : "NONE "); : (fl->fl_type & LOCK_WRITE) ? "WRITE" : "NONE ");
} else { } else {
out += sprintf(out, "%s ", out += sprintf(out, "%s ",
(fl->fl_type & F_WRLCK) ? "WRITE" : "READ "); (fl->fl_type & F_INPROGRESS)
? (fl->fl_type & F_UNLCK) ? "UNLCK" : "READ "
: (fl->fl_type & F_WRLCK) ? "WRITE" : "READ ");
} }
out += sprintf(out, "%d %s:%ld ", out += sprintf(out, "%d %s:%ld ",
fl->fl_pid, fl->fl_pid,
......
...@@ -554,6 +554,7 @@ struct file_lock { ...@@ -554,6 +554,7 @@ struct file_lock {
void (*fl_remove)(struct file_lock *); /* lock removal callback */ void (*fl_remove)(struct file_lock *); /* lock removal callback */
struct fasync_struct * fl_fasync; /* for lease break notifications */ struct fasync_struct * fl_fasync; /* for lease break notifications */
unsigned long fl_break_time; /* for nonblocking lease breaks */
union { union {
struct nfs_lock_info nfs_fl; struct nfs_lock_info nfs_fl;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment