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

Configure Feed

Select the types of activity you want to include in your feed.

NFS: Avoid changing nlink when file removes and attribute updates race

If a file removal races with another operation that updates its
attributes, then skip the change to nlink, and just mark the attributes
as being stale.

Reported-by: Aiden Lambert <alambert48@gatech.edu>
Fixes: 59a707b0d42e ("NFS: Ensure we revalidate the inode correctly after remove or rename")
Signed-off-by: Trond Myklebust <trond.myklebust@hammerspace.com>

+13 -6
+13 -6
fs/nfs/dir.c
··· 1894 1894 } 1895 1895 1896 1896 /* Ensure that we revalidate inode->i_nlink */ 1897 - static void nfs_drop_nlink(struct inode *inode) 1897 + static void nfs_drop_nlink(struct inode *inode, unsigned long gencount) 1898 1898 { 1899 + struct nfs_inode *nfsi = NFS_I(inode); 1900 + 1899 1901 spin_lock(&inode->i_lock); 1900 1902 /* drop the inode if we're reasonably sure this is the last link */ 1901 - if (inode->i_nlink > 0) 1903 + if (inode->i_nlink > 0 && gencount == nfsi->attr_gencount) 1902 1904 drop_nlink(inode); 1903 - NFS_I(inode)->attr_gencount = nfs_inc_attr_generation_counter(); 1905 + nfsi->attr_gencount = nfs_inc_attr_generation_counter(); 1904 1906 nfs_set_cache_invalid( 1905 1907 inode, NFS_INO_INVALID_CHANGE | NFS_INO_INVALID_CTIME | 1906 1908 NFS_INO_INVALID_NLINK); ··· 1916 1914 static void nfs_dentry_iput(struct dentry *dentry, struct inode *inode) 1917 1915 { 1918 1916 if (dentry->d_flags & DCACHE_NFSFS_RENAMED) { 1917 + unsigned long gencount = READ_ONCE(NFS_I(inode)->attr_gencount); 1919 1918 nfs_complete_unlink(dentry, inode); 1920 - nfs_drop_nlink(inode); 1919 + nfs_drop_nlink(inode, gencount); 1921 1920 } 1922 1921 iput(inode); 1923 1922 } ··· 2510 2507 2511 2508 trace_nfs_remove_enter(dir, dentry); 2512 2509 if (inode != NULL) { 2510 + unsigned long gencount = READ_ONCE(NFS_I(inode)->attr_gencount); 2511 + 2513 2512 error = NFS_PROTO(dir)->remove(dir, dentry); 2514 2513 if (error == 0) 2515 - nfs_drop_nlink(inode); 2514 + nfs_drop_nlink(inode, gencount); 2516 2515 } else 2517 2516 error = NFS_PROTO(dir)->remove(dir, dentry); 2518 2517 if (error == -ENOENT) ··· 2714 2709 { 2715 2710 struct inode *old_inode = d_inode(old_dentry); 2716 2711 struct inode *new_inode = d_inode(new_dentry); 2712 + unsigned long new_gencount = 0; 2717 2713 struct dentry *dentry = NULL; 2718 2714 struct rpc_task *task; 2719 2715 bool must_unblock = false; ··· 2767 2761 } else { 2768 2762 block_revalidate(new_dentry); 2769 2763 must_unblock = true; 2764 + new_gencount = NFS_I(new_inode)->attr_gencount; 2770 2765 spin_unlock(&new_dentry->d_lock); 2771 2766 } 2772 2767 ··· 2807 2800 new_dir, new_dentry, error); 2808 2801 if (!error) { 2809 2802 if (new_inode != NULL) 2810 - nfs_drop_nlink(new_inode); 2803 + nfs_drop_nlink(new_inode, new_gencount); 2811 2804 /* 2812 2805 * The d_move() should be here instead of in an async RPC completion 2813 2806 * handler because we need the proper locks to move the dentry. If