diff options
-rw-r--r-- | fs/locks.c | 75 | ||||
-rw-r--r-- | include/linux/fs.h | 6 |
2 files changed, 68 insertions, 13 deletions
diff --git a/fs/locks.c b/fs/locks.c index 92a0f0a52b06..2cfeea622f28 100644 --- a/fs/locks.c +++ b/fs/locks.c | |||
@@ -652,15 +652,18 @@ static void locks_insert_lock(struct file_lock **pos, struct file_lock *fl) | |||
652 | locks_insert_global_locks(fl); | 652 | locks_insert_global_locks(fl); |
653 | } | 653 | } |
654 | 654 | ||
655 | /* | 655 | /** |
656 | * Delete a lock and then free it. | 656 | * locks_delete_lock - Delete a lock and then free it. |
657 | * Wake up processes that are blocked waiting for this lock, | 657 | * @thisfl_p: pointer that points to the fl_next field of the previous |
658 | * notify the FS that the lock has been cleared and | 658 | * inode->i_flock list entry |
659 | * finally free the lock. | 659 | * |
660 | * Unlink a lock from all lists and free the namespace reference, but don't | ||
661 | * free it yet. Wake up processes that are blocked waiting for this lock and | ||
662 | * notify the FS that the lock has been cleared. | ||
660 | * | 663 | * |
661 | * Must be called with the i_lock held! | 664 | * Must be called with the i_lock held! |
662 | */ | 665 | */ |
663 | static void locks_delete_lock(struct file_lock **thisfl_p) | 666 | static void locks_unlink_lock(struct file_lock **thisfl_p) |
664 | { | 667 | { |
665 | struct file_lock *fl = *thisfl_p; | 668 | struct file_lock *fl = *thisfl_p; |
666 | 669 | ||
@@ -675,6 +678,18 @@ static void locks_delete_lock(struct file_lock **thisfl_p) | |||
675 | } | 678 | } |
676 | 679 | ||
677 | locks_wake_up_blocks(fl); | 680 | locks_wake_up_blocks(fl); |
681 | } | ||
682 | |||
683 | /* | ||
684 | * Unlink a lock from all lists and free it. | ||
685 | * | ||
686 | * Must be called with i_lock held! | ||
687 | */ | ||
688 | static void locks_delete_lock(struct file_lock **thisfl_p) | ||
689 | { | ||
690 | struct file_lock *fl = *thisfl_p; | ||
691 | |||
692 | locks_unlink_lock(thisfl_p); | ||
678 | locks_free_lock(fl); | 693 | locks_free_lock(fl); |
679 | } | 694 | } |
680 | 695 | ||
@@ -1472,6 +1487,32 @@ int fcntl_getlease(struct file *filp) | |||
1472 | return type; | 1487 | return type; |
1473 | } | 1488 | } |
1474 | 1489 | ||
1490 | /** | ||
1491 | * check_conflicting_open - see if the given dentry points to a file that has | ||
1492 | * an existing open that would conflict with the | ||
1493 | * desired lease. | ||
1494 | * @dentry: dentry to check | ||
1495 | * @arg: type of lease that we're trying to acquire | ||
1496 | * | ||
1497 | * Check to see if there's an existing open fd on this file that would | ||
1498 | * conflict with the lease we're trying to set. | ||
1499 | */ | ||
1500 | static int | ||
1501 | check_conflicting_open(const struct dentry *dentry, const long arg) | ||
1502 | { | ||
1503 | int ret = 0; | ||
1504 | struct inode *inode = dentry->d_inode; | ||
1505 | |||
1506 | if ((arg == F_RDLCK) && (atomic_read(&inode->i_writecount) > 0)) | ||
1507 | return -EAGAIN; | ||
1508 | |||
1509 | if ((arg == F_WRLCK) && ((d_count(dentry) > 1) || | ||
1510 | (atomic_read(&inode->i_count) > 1))) | ||
1511 | ret = -EAGAIN; | ||
1512 | |||
1513 | return ret; | ||
1514 | } | ||
1515 | |||
1475 | static int generic_add_lease(struct file *filp, long arg, struct file_lock **flp) | 1516 | static int generic_add_lease(struct file *filp, long arg, struct file_lock **flp) |
1476 | { | 1517 | { |
1477 | struct file_lock *fl, **before, **my_before = NULL, *lease; | 1518 | struct file_lock *fl, **before, **my_before = NULL, *lease; |
@@ -1499,12 +1540,8 @@ static int generic_add_lease(struct file *filp, long arg, struct file_lock **flp | |||
1499 | return -EINVAL; | 1540 | return -EINVAL; |
1500 | } | 1541 | } |
1501 | 1542 | ||
1502 | error = -EAGAIN; | 1543 | error = check_conflicting_open(dentry, arg); |
1503 | if ((arg == F_RDLCK) && (atomic_read(&inode->i_writecount) > 0)) | 1544 | if (error) |
1504 | goto out; | ||
1505 | if ((arg == F_WRLCK) | ||
1506 | && ((d_count(dentry) > 1) | ||
1507 | || (atomic_read(&inode->i_count) > 1))) | ||
1508 | goto out; | 1545 | goto out; |
1509 | 1546 | ||
1510 | /* | 1547 | /* |
@@ -1549,7 +1586,19 @@ static int generic_add_lease(struct file *filp, long arg, struct file_lock **flp | |||
1549 | goto out; | 1586 | goto out; |
1550 | 1587 | ||
1551 | locks_insert_lock(before, lease); | 1588 | locks_insert_lock(before, lease); |
1552 | error = 0; | 1589 | /* |
1590 | * The check in break_lease() is lockless. It's possible for another | ||
1591 | * open to race in after we did the earlier check for a conflicting | ||
1592 | * open but before the lease was inserted. Check again for a | ||
1593 | * conflicting open and cancel the lease if there is one. | ||
1594 | * | ||
1595 | * We also add a barrier here to ensure that the insertion of the lock | ||
1596 | * precedes these checks. | ||
1597 | */ | ||
1598 | smp_mb(); | ||
1599 | error = check_conflicting_open(dentry, arg); | ||
1600 | if (error) | ||
1601 | locks_unlink_lock(flp); | ||
1553 | out: | 1602 | out: |
1554 | if (is_deleg) | 1603 | if (is_deleg) |
1555 | mutex_unlock(&inode->i_mutex); | 1604 | mutex_unlock(&inode->i_mutex); |
diff --git a/include/linux/fs.h b/include/linux/fs.h index 09f553c59813..df8474408331 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h | |||
@@ -1964,6 +1964,12 @@ static inline int locks_verify_truncate(struct inode *inode, | |||
1964 | 1964 | ||
1965 | static inline int break_lease(struct inode *inode, unsigned int mode) | 1965 | static inline int break_lease(struct inode *inode, unsigned int mode) |
1966 | { | 1966 | { |
1967 | /* | ||
1968 | * Since this check is lockless, we must ensure that any refcounts | ||
1969 | * taken are done before checking inode->i_flock. Otherwise, we could | ||
1970 | * end up racing with tasks trying to set a new lease on this file. | ||
1971 | */ | ||
1972 | smp_mb(); | ||
1967 | if (inode->i_flock) | 1973 | if (inode->i_flock) |
1968 | return __break_lease(inode, mode, FL_LEASE); | 1974 | return __break_lease(inode, mode, FL_LEASE); |
1969 | return 0; | 1975 | return 0; |