Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

filelock: allow lease_managers to dictate what qualifies as a conflict

Requesting a delegation on a file from the userland fcntl() interface
currently succeeds when there are conflicting opens present.

This is because the lease handling code ignores conflicting opens for
FL_LAYOUT and FL_DELEG leases. This was a hack put in place long ago,
because nfsd already checks for conflicts in its own way. The kernel
needs to perform this check for userland delegations the same way it is
done for leases, however.

Make this dependent on the lease_manager by adding a new
->lm_open_conflict() lease_manager operation and have
generic_add_lease() call that instead of check_conflicting_open().
Morph check_conflicting_open() into a ->lm_open_conflict() op that is
only called for userland leases/delegations. Set the
->lm_open_conflict() operations for nfsd to trivial functions that
always return 0.

Reviewed-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Link: https://patch.msgid.link/20251204-dir-deleg-ro-v2-2-22d37f92ce2c@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>

authored by

Jeff Layton and committed by
Christian Brauner
12965a19 392e317a

+84 -50
+1
Documentation/filesystems/locking.rst
··· 416 416 lm_breaker_owns_lease: yes no no 417 417 lm_lock_expirable yes no no 418 418 lm_expire_lock no no yes 419 + lm_open_conflict yes no no 419 420 ====================== ============= ================= ========= 420 421 421 422 buffer_head
+42 -48
fs/locks.c
··· 585 585 __f_setown(filp, task_pid(current), PIDTYPE_TGID, 0); 586 586 } 587 587 588 + /** 589 + * lease_open_conflict - see if the given file points to an inode that has 590 + * an existing open that would conflict with the 591 + * desired lease. 592 + * @filp: file to check 593 + * @arg: type of lease that we're trying to acquire 594 + * 595 + * Check to see if there's an existing open fd on this file that would 596 + * conflict with the lease we're trying to set. 597 + */ 598 + static int 599 + lease_open_conflict(struct file *filp, const int arg) 600 + { 601 + struct inode *inode = file_inode(filp); 602 + int self_wcount = 0, self_rcount = 0; 603 + 604 + if (arg == F_RDLCK) 605 + return inode_is_open_for_write(inode) ? -EAGAIN : 0; 606 + else if (arg != F_WRLCK) 607 + return 0; 608 + 609 + /* 610 + * Make sure that only read/write count is from lease requestor. 611 + * Note that this will result in denying write leases when i_writecount 612 + * is negative, which is what we want. (We shouldn't grant write leases 613 + * on files open for execution.) 614 + */ 615 + if (filp->f_mode & FMODE_WRITE) 616 + self_wcount = 1; 617 + else if (filp->f_mode & FMODE_READ) 618 + self_rcount = 1; 619 + 620 + if (atomic_read(&inode->i_writecount) != self_wcount || 621 + atomic_read(&inode->i_readcount) != self_rcount) 622 + return -EAGAIN; 623 + 624 + return 0; 625 + } 626 + 588 627 static const struct lease_manager_operations lease_manager_ops = { 589 628 .lm_break = lease_break_callback, 590 629 .lm_change = lease_modify, 591 630 .lm_setup = lease_setup, 631 + .lm_open_conflict = lease_open_conflict, 592 632 }; 593 633 594 634 /* ··· 1794 1754 return 0; 1795 1755 } 1796 1756 1797 - /** 1798 - * check_conflicting_open - see if the given file points to an inode that has 1799 - * an existing open that would conflict with the 1800 - * desired lease. 1801 - * @filp: file to check 1802 - * @arg: type of lease that we're trying to acquire 1803 - * @flags: current lock flags 1804 - * 1805 - * Check to see if there's an existing open fd on this file that would 1806 - * conflict with the lease we're trying to set. 1807 - */ 1808 - static int 1809 - check_conflicting_open(struct file *filp, const int arg, int flags) 1810 - { 1811 - struct inode *inode = file_inode(filp); 1812 - int self_wcount = 0, self_rcount = 0; 1813 - 1814 - if (flags & FL_LAYOUT) 1815 - return 0; 1816 - if (flags & FL_DELEG) 1817 - /* We leave these checks to the caller */ 1818 - return 0; 1819 - 1820 - if (arg == F_RDLCK) 1821 - return inode_is_open_for_write(inode) ? -EAGAIN : 0; 1822 - else if (arg != F_WRLCK) 1823 - return 0; 1824 - 1825 - /* 1826 - * Make sure that only read/write count is from lease requestor. 1827 - * Note that this will result in denying write leases when i_writecount 1828 - * is negative, which is what we want. (We shouldn't grant write leases 1829 - * on files open for execution.) 1830 - */ 1831 - if (filp->f_mode & FMODE_WRITE) 1832 - self_wcount = 1; 1833 - else if (filp->f_mode & FMODE_READ) 1834 - self_rcount = 1; 1835 - 1836 - if (atomic_read(&inode->i_writecount) != self_wcount || 1837 - atomic_read(&inode->i_readcount) != self_rcount) 1838 - return -EAGAIN; 1839 - 1840 - return 0; 1841 - } 1842 - 1843 1757 static int 1844 1758 generic_add_lease(struct file *filp, int arg, struct file_lease **flp, void **priv) 1845 1759 { ··· 1830 1836 percpu_down_read(&file_rwsem); 1831 1837 spin_lock(&ctx->flc_lock); 1832 1838 time_out_leases(inode, &dispose); 1833 - error = check_conflicting_open(filp, arg, lease->c.flc_flags); 1839 + error = lease->fl_lmops->lm_open_conflict(filp, arg); 1834 1840 if (error) 1835 1841 goto out; 1836 1842 ··· 1887 1893 * precedes these checks. 1888 1894 */ 1889 1895 smp_mb(); 1890 - error = check_conflicting_open(filp, arg, lease->c.flc_flags); 1896 + error = lease->fl_lmops->lm_open_conflict(filp, arg); 1891 1897 if (error) { 1892 1898 locks_unlink_lock_ctx(&lease->c); 1893 1899 goto out;
+21 -2
fs/nfsd/nfs4layouts.c
··· 764 764 return lease_modify(onlist, arg, dispose); 765 765 } 766 766 767 + /** 768 + * nfsd4_layout_lm_open_conflict - see if the given file points to an inode that has 769 + * an existing open that would conflict with the 770 + * desired lease. 771 + * @filp: file to check 772 + * @arg: type of lease that we're trying to acquire 773 + * 774 + * The kernel will call into this operation to determine whether there 775 + * are conflicting opens that may prevent the layout from being granted. 776 + * For nfsd, that check is done at a higher level, so this trivially 777 + * returns 0. 778 + */ 779 + static int 780 + nfsd4_layout_lm_open_conflict(struct file *filp, int arg) 781 + { 782 + return 0; 783 + } 784 + 767 785 static const struct lease_manager_operations nfsd4_layouts_lm_ops = { 768 - .lm_break = nfsd4_layout_lm_break, 769 - .lm_change = nfsd4_layout_lm_change, 786 + .lm_break = nfsd4_layout_lm_break, 787 + .lm_change = nfsd4_layout_lm_change, 788 + .lm_open_conflict = nfsd4_layout_lm_open_conflict, 770 789 }; 771 790 772 791 int
+19
fs/nfsd/nfs4state.c
··· 5552 5552 return -EAGAIN; 5553 5553 } 5554 5554 5555 + /** 5556 + * nfsd4_deleg_lm_open_conflict - see if the given file points to an inode that has 5557 + * an existing open that would conflict with the 5558 + * desired lease. 5559 + * @filp: file to check 5560 + * @arg: type of lease that we're trying to acquire 5561 + * 5562 + * The kernel will call into this operation to determine whether there 5563 + * are conflicting opens that may prevent the deleg from being granted. 5564 + * For nfsd, that check is done at a higher level, so this trivially 5565 + * returns 0. 5566 + */ 5567 + static int 5568 + nfsd4_deleg_lm_open_conflict(struct file *filp, int arg) 5569 + { 5570 + return 0; 5571 + } 5572 + 5555 5573 static const struct lease_manager_operations nfsd_lease_mng_ops = { 5556 5574 .lm_breaker_owns_lease = nfsd_breaker_owns_lease, 5557 5575 .lm_break = nfsd_break_deleg_cb, 5558 5576 .lm_change = nfsd_change_deleg_cb, 5577 + .lm_open_conflict = nfsd4_deleg_lm_open_conflict, 5559 5578 }; 5560 5579 5561 5580 static __be32 nfsd4_check_seqid(struct nfsd4_compound_state *cstate, struct nfs4_stateowner *so, u32 seqid)
+1
include/linux/filelock.h
··· 49 49 int (*lm_change)(struct file_lease *, int, struct list_head *); 50 50 void (*lm_setup)(struct file_lease *, void **); 51 51 bool (*lm_breaker_owns_lease)(struct file_lease *); 52 + int (*lm_open_conflict)(struct file *, int); 52 53 }; 53 54 54 55 struct lock_manager {