return (error < 0)? nlm_lck_denied_nolocks : nlm_granted;
}
@@ -533,7 +533,7 @@
* following yields an error, this is most probably due to low
* memory. Retry the lock in a few seconds.
*/
- if ((error = posix_lock_file(&file->f_file, &lock->fl, 0)) < 0) {
+ if ((error = posix_lock_file(&file->f_file, &lock->fl)) < 0) {
printk(KERN_WARNING "lockd: unexpected error %d in %s!\n",
-error, __FUNCTION__);
nlmsvc_insert_block(block, 10 * HZ);
diff -urNX dontdiff linux-2.5.22/fs/lockd/svcsubs.c linux-2.5.22-flock/fs/lockd/svcsubs.c
--- linux-2.5.22/fs/lockd/svcsubs.c Sun Jun 2 18:44:41 2002
+++ linux-2.5.22-flock/fs/lockd/svcsubs.c Fri Jun 14 06:19:17 2002
@@ -176,7 +176,7 @@
lock.fl_type = F_UNLCK;
lock.fl_start = 0;
lock.fl_end = OFFSET_MAX;
- if (posix_lock_file(&file->f_file, &lock, 0) < 0) {
+ if (posix_lock_file(&file->f_file, &lock) < 0) {
printk("lockd: unlock failure in %s:%d\n",
__FILE__, __LINE__);
return 1;
diff -urNX dontdiff linux-2.5.22/fs/locks.c linux-2.5.22-flock/fs/locks.c
--- linux-2.5.22/fs/locks.c Mon Jun 17 04:49:08 2002
+++ linux-2.5.22-flock/fs/locks.c Tue Jun 18 12:55:44 2002
@@ -184,7 +184,6 @@
fl->fl_file = NULL;
fl->fl_flags = 0;
fl->fl_type = 0;
- fl->fl_start = fl->fl_end = 0;
fl->fl_notify = NULL;
fl->fl_insert = NULL;
fl->fl_remove = NULL;
@@ -254,8 +253,12 @@
fl->fl_pid = current->pid;
fl->fl_flags = FL_FLOCK;
fl->fl_type = type;
+ fl->fl_start = 0;
fl->fl_end = OFFSET_MAX;
-
+
+ if ((cmd & LOCK_NB) == 0)
+ fl->fl_flags |= FL_SLEEP;
+
*lock = fl;
return 0;
}
@@ -465,6 +468,8 @@
*/
static void locks_insert_lock(struct file_lock **pos, struct file_lock *fl)
{
+ if (fl->fl_type == F_UNLCK)
+ return;
list_add(&fl->fl_link, &file_lock_list);
/* insert into file's list */
@@ -767,8 +772,7 @@
* at the head of the list, but that's secret knowledge known only to
* flock_lock_file and posix_lock_file.
*/
-static int flock_lock_file(struct file *filp, struct file_lock *new_fl,
- unsigned int wait)
+static int flock_lock_file(struct file *filp, struct file_lock *new_fl)
{
struct file_lock **before;
struct inode * inode = filp->f_dentry->d_inode;
@@ -799,7 +803,6 @@
return 0;
+/*
+ * posix_juggle_locks - Attempt to merge POSIX locks
+ * @caller: new lock
+ * @fl: existing lock
+ * @returns 0 to continue looking, 1 to stop looking and -1 to indicate
+ * that the current lock should be deleted.
+ *
+ * Attempt to merge these two locks. Due to the heinous POSIX locking
+ * semantics, we may end up having to split an existing lock into three
+ * pieces, so we need an extra lock.
+ */
+static int posix_juggle_locks(struct file_lock *caller,
+ struct file_lock *fl, struct file_lock *extra)
+{
+ if (caller->fl_type == fl->fl_type) {
+ if (fl->fl_end < caller->fl_start - 1)
+ return 0;
+ if (fl->fl_start > caller->fl_end + 1)
+ return 1;
+
+ /* The new and old lock are of the same type and should be
+ * merged. Extend the new lock to be the union of the
+ * two locks and delete the existing lock.
+ */
+ if (fl->fl_start < caller->fl_start) {
+ caller->fl_start = fl->fl_start;
+ }
+ if (fl->fl_end > caller->fl_end) {
+ caller->fl_end = fl->fl_end;
+ }
+ return -1;
+ } else {
+ if (fl->fl_end < caller->fl_start)
+ return 0;
+ if (fl->fl_start > caller->fl_end)
+ return 1;
+
+ printk(KERN_DEBUG "locks: caller %d %Lx-%Lx, existing %d %Lx-%Lx\n",
+ caller->fl_type, caller->fl_start, caller->fl_end,
+ fl->fl_type, fl->fl_start, fl->fl_end);
+ /* We have an overlap of some type. */
+ if (caller->fl_start <= fl->fl_start) {
+ if (fl->fl_end <= caller->fl_end) {
+ /* We completely replace this lock. Just delete it. */
+ return -1;
+ }
+ fl->fl_start = caller->fl_end + 1;
+ /* Maybe the changed size & type allows others to progress */
+ locks_wake_up_blocks(fl);
+ return 1;
+ }
+
+ if (fl->fl_end <= caller->fl_end) {
+ fl->fl_end = caller->fl_start - 1;
+ /* Maybe the changed size & type allows others to progress */
+ locks_wake_up_blocks(fl);
+ return 1;
+ }
+
+ /* The new lock splits the old lock. POSIX, we hatesss it */
+ locks_copy_lock(extra, fl);
+ fl->fl_end = caller->fl_start - 1;
+ extra->fl_start = caller->fl_end + 1;
+ locks_insert_lock(&fl->fl_next, extra);
+ locks_wake_up_blocks(fl);
+ return 1;
+ }
+}
+
/**
- * posix_lock_file:
- * @filp: The file to apply the lock to
- * @caller: The lock to be applied
- * @wait: 1 to retry automatically, 0 to return -EAGAIN
+ * __posix_lock_file:
+ * @filp: The file to apply the lock to
+ * @caller: The lock to be applied
*
* Add a POSIX style lock to a file.
- * We merge adjacent locks whenever possible. POSIX locks are sorted by owner
- * task, then by starting address
- *
- * Kai Petzke writes:
- * To make freeing a lock much faster, we keep a pointer to the lock before the
- * actual one. But the real gain of the new coding was, that lock_it() and
- * unlock_it() became one function.
- *
- * To all purists: Yes, I use a few goto's. Just pass on to the next function.
+ * Locks are merged and split as appropriate.
+ * POSIX locks are sorted by owner task, then by starting address
*/
/*
- * We may need two file_lock structures for this operation,
- * so we get them in advance to avoid races.
+ * We may need an extra file_lock structure for this operation,
+ * so get it in advance to avoid atomic allocation.
*/
- new_fl = locks_alloc_lock(0);
- new_fl2 = locks_alloc_lock(0);
error = -ENOLCK; /* "no luck" */
- if (!(new_fl && new_fl2))
+ extra = locks_alloc_lock(0);
+ if (!extra)
goto out_nolock;
- /*
- * We've allocated the new locks in advance, so there are no
- * errors possible (and no blocking operations) from here on.
- *
- * Find the first old lock with the same owner as the new lock.
- */
-
- before = &inode->i_flock;
-
- /* First skip locks owned by other processes.
- */
- while ((fl = *before) && (!IS_POSIX(fl) ||
- !locks_same_owner(caller, fl))) {
- before = &fl->fl_next;
- }
-
- /* Process locks with this owner.
- */
- while ((fl = *before) && locks_same_owner(caller, fl)) {
- /* Detect adjacent or overlapping regions (if same lock type)
- */
- if (caller->fl_type == fl->fl_type) {
- if (fl->fl_end < caller->fl_start - 1)
- goto next_lock;
- /* If the next lock in the list has entirely bigger
- * addresses than the new one, insert the lock here.
- */
- if (fl->fl_start > caller->fl_end + 1)
+ for_each_lock(inode, before) {
+ int result;
+ struct file_lock *fl;
+ deleted:
+ fl = *before;
+ if (!IS_POSIX(fl))
+ continue;
+ if (!locks_same_owner(caller, fl)) {
+ if (found)
break;
-
- /* If we come here, the new and old lock are of the
- * same type and adjacent or overlapping. Make one
- * lock yielding from the lower start address of both
- * locks to the higher end address.
- */
- if (fl->fl_start > caller->fl_start)
- fl->fl_start = caller->fl_start;
else
- caller->fl_start = fl->fl_start;
- if (fl->fl_end < caller->fl_end)
- fl->fl_end = caller->fl_end;
- else
- caller->fl_end = fl->fl_end;
- if (added) {
- locks_delete_lock(before);
continue;
- }
- caller = fl;
- added = 1;
}
- else {
- /* Processing for different lock types is a bit
- * more complex.
- */
- if (fl->fl_end < caller->fl_start)
- goto next_lock;
- if (fl->fl_start > caller->fl_end)
- break;
- if (caller->fl_type == F_UNLCK)
- added = 1;
- if (fl->fl_start < caller->fl_start)
- left = fl;
- /* If the next lock in the list has a higher end
- * address than the new one, insert the new one here.
- */
- if (fl->fl_end > caller->fl_end) {
- right = fl;
- break;
- }
- if (fl->fl_start >= caller->fl_start) {
- /* The new lock completely replaces an old
- * one (This may happen several times).
- */
- if (added) {
- locks_delete_lock(before);
- continue;
- }
- /* Replace the old lock with the new one.
- * Wake up anybody waiting for the old one,
- * as the change in lock type might satisfy
- * their needs.
- */
- locks_wake_up_blocks(fl);
- fl->fl_start = caller->fl_start;
- fl->fl_end = caller->fl_end;
- fl->fl_type = caller->fl_type;
- fl->fl_u = caller->fl_u;
- caller = fl;
- added = 1;
- }
+ found = 1;
+ result = posix_juggle_locks(caller, fl, extra);
+ if (result == 0)
+ continue;
+ if (result == -1) {
+ locks_delete_lock(before);
+ if (*before)
+ goto deleted;
}
- /* Go on to next lock.
- */
- next_lock:
- before = &fl->fl_next;
+ break;
}
+ /* If we had to use the extra lock, we shouldn't free it */
+ if (!list_empty(&extra->fl_link))
+ extra = NULL;
+ locks_insert_lock(before, caller);
error = 0;
- if (!added) {
- if (caller->fl_type == F_UNLCK)
- goto out;
- locks_copy_lock(new_fl, caller);
- locks_insert_lock(before, new_fl);
- new_fl = NULL;
- }
- if (right) {
- if (left == right) {
- /* The new lock breaks the old one in two pieces,
- * so we have to use the second new lock.
- */
- left = new_fl2;
- new_fl2 = NULL;
- locks_copy_lock(left, right);
- locks_insert_lock(before, left);
- }
- right->fl_start = caller->fl_end + 1;
- locks_wake_up_blocks(right);
- }
- if (left) {
- left->fl_end = caller->fl_start - 1;
- locks_wake_up_blocks(left);
- }
+
out:
unlock_kernel();
out_nolock:
/*
* Free any unused locks.
*/
- if (new_fl)
- locks_free_lock(new_fl);
- if (new_fl2)
- locks_free_lock(new_fl2);
+ if (extra)
+ locks_free_lock(extra);
+ return error;
+}
+
+/* Called from nfsd/lockd. They embed the struct file_lock in their
+ * own structs, so if we use it directly we'll try and free it incorrectly
+ */
+int posix_lock_file(struct file *filp, struct file_lock *caller)
+{
+ int error;
+ struct file_lock *copy = locks_alloc_lock(0);
+ locks_copy_lock(copy, caller);
+ error = __posix_lock_file(filp, copy);
+ if (error) {
+ locks_free_lock(copy);
+ }
return error;
}
@@ -1303,11 +1279,23 @@
if (error < 0)
goto out_putf;