| @@ -986,14 +986,113 @@ static int nfs_fsync_dir(struct file *filp, loff_t start, loff_t end, · 985 unmodified lines |
| 986 | 986 | * full lookup on all child dentries of 'dir' whenever a change occurs |
| 987 | 987 | * on the server that might have invalidated our dcache. |
| 988 | 988 | * |
| 989 | + * Note that we reserve bit '0' as a tag to let us know when a dentry |
| 990 | + * was revalidated while holding a delegation on its inode. |
| 991 | + * |
| 989 | 992 | * The caller should be holding dir->i_lock |
| 990 | 993 | */ |
| 991 | 994 | void nfs_force_lookup_revalidate(struct inode *dir) |
| 992 | 995 | { |
| 993 | | − NFS_I(dir)->cache_change_attribute++; |
| 996 | + NFS_I(dir)->cache_change_attribute += 2; |
| 994 | 997 | } |
| 995 | 998 | EXPORT_SYMBOL_GPL(nfs_force_lookup_revalidate); |
| 996 | 999 | |
| 1000 | +/** |
| 1001 | + * nfs_verify_change_attribute - Detects NFS remote directory changes |
| 1002 | + * @dir: pointer to parent directory inode |
| 1003 | + * @verf: previously saved change attribute |
| 1004 | + * |
| 1005 | + * Return "false" if the verifiers doesn't match the change attribute. |
| 1006 | + * This would usually indicate that the directory contents have changed on |
| 1007 | + * the server, and that any dentries need revalidating. |
| 1008 | + */ |
| 1009 | +static bool nfs_verify_change_attribute(struct inode *dir, unsigned long verf) |
| 1010 | +{ |
| 1011 | + return (verf & ~1UL) == nfs_save_change_attribute(dir); |
| 1012 | +} |
| 1013 | + |
| 1014 | +static void nfs_set_verifier_delegated(unsigned long *verf) |
| 1015 | +{ |
| 1016 | + *verf |= 1UL; |
| 1017 | +} |
| 1018 | + |
| 1019 | +#if IS_ENABLED(CONFIG_NFS_V4) |
| 1020 | +static void nfs_unset_verifier_delegated(unsigned long *verf) |
| 1021 | +{ |
| 1022 | + *verf &= ~1UL; |
| 1023 | +} |
| 1024 | +#endif /* IS_ENABLED(CONFIG_NFS_V4) */ |
| 1025 | + |
| 1026 | +static bool nfs_test_verifier_delegated(unsigned long verf) |
| 1027 | +{ |
| 1028 | + return verf & 1; |
| 1029 | +} |
| 1030 | + |
| 1031 | +static bool nfs_verifier_is_delegated(struct dentry *dentry) |
| 1032 | +{ |
| 1033 | + return nfs_test_verifier_delegated(dentry->d_time); |
| 1034 | +} |
| 1035 | + |
| 1036 | +static void nfs_set_verifier_locked(struct dentry *dentry, unsigned long verf) |
| 1037 | +{ |
| 1038 | + struct inode *inode = d_inode(dentry); |
| 1039 | + |
| 1040 | + if (!nfs_verifier_is_delegated(dentry) && |
| 1041 | + !nfs_verify_change_attribute(d_inode(dentry->d_parent), verf)) |
| 1042 | + goto out; |
| 1043 | + if (inode && NFS_PROTO(inode)->have_delegation(inode, FMODE_READ)) |
| 1044 | + nfs_set_verifier_delegated(&verf); |
| 1045 | +out: |
| 1046 | + dentry->d_time = verf; |
| 1047 | +} |
| 1048 | + |
| 1049 | +/** |
| 1050 | + * nfs_set_verifier - save a parent directory verifier in the dentry |
| 1051 | + * @dentry: pointer to dentry |
| 1052 | + * @verf: verifier to save |
| 1053 | + * |
| 1054 | + * Saves the parent directory verifier in @dentry. If the inode has |
| 1055 | + * a delegation, we also tag the dentry as having been revalidated |
| 1056 | + * while holding a delegation so that we know we don't have to |
| 1057 | + * look it up again after a directory change. |
| 1058 | + */ |
| 1059 | +void nfs_set_verifier(struct dentry *dentry, unsigned long verf) |
| 1060 | +{ |
| 1061 | + |
| 1062 | + spin_lock(&dentry->d_lock); |
| 1063 | + nfs_set_verifier_locked(dentry, verf); |
| 1064 | + spin_unlock(&dentry->d_lock); |
| 1065 | +} |
| 1066 | +EXPORT_SYMBOL_GPL(nfs_set_verifier); |
| 1067 | + |
| 1068 | +#if IS_ENABLED(CONFIG_NFS_V4) |
| 1069 | +/** |
| 1070 | + * nfs_clear_verifier_delegated - clear the dir verifier delegation tag |
| 1071 | + * @inode: pointer to inode |
| 1072 | + * |
| 1073 | + * Iterates through the dentries in the inode alias list and clears |
| 1074 | + * the tag used to indicate that the dentry has been revalidated |
| 1075 | + * while holding a delegation. |
| 1076 | + * This function is intended for use when the delegation is being |
| 1077 | + * returned or revoked. |
| 1078 | + */ |
| 1079 | +void nfs_clear_verifier_delegated(struct inode *inode) |
| 1080 | +{ |
| 1081 | + struct dentry *alias; |
| 1082 | + |
| 1083 | + if (!inode) |
| 1084 | + return; |
| 1085 | + spin_lock(&inode->i_lock); |
| 1086 | + hlist_for_each_entry(alias, &inode->i_dentry, d_u.d_alias) { |
| 1087 | + spin_lock(&alias->d_lock); |
| 1088 | + nfs_unset_verifier_delegated(&alias->d_time); |
| 1089 | + spin_unlock(&alias->d_lock); |
| 1090 | + } |
| 1091 | + spin_unlock(&inode->i_lock); |
| 1092 | +} |
| 1093 | +EXPORT_SYMBOL_GPL(nfs_clear_verifier_delegated); |
| 1094 | +#endif /* IS_ENABLED(CONFIG_NFS_V4) */ |
| 1095 | + |
| 997 | 1096 | /* |
| 998 | 1097 | * A check for whether or not the parent directory has changed. |
| 999 | 1098 | * In the case it has, we assume that the dentries are untrustworthy |
| @@ -1235,7 +1334,7 @@ nfs_do_lookup_revalidate(struct inode *dir, struct dentry *dentry, · 235 unmodified lines |
| 1235 | 1334 | goto out_bad; |
| 1236 | 1335 | } |
| 1237 | 1336 | |
| 1238 | | − if (NFS_PROTO(dir)->have_delegation(inode, FMODE_READ)) |
| 1337 | + if (nfs_verifier_is_delegated(dentry)) |
| 1239 | 1338 | return nfs_lookup_revalidate_delegated(dir, dentry, inode); |
| 1240 | 1339 | |
| 1241 | 1340 | /* Force a full look up iff the parent directory has changed */ |
| @@ -1675,7 +1774,7 @@ nfs4_do_lookup_revalidate(struct inode *dir, struct dentry *dentry, · 433 unmodified lines |
| 1675 | 1774 | if (inode == NULL) |
| 1676 | 1775 | goto full_reval; |
| 1677 | 1776 | |
| 1678 | | − if (NFS_PROTO(dir)->have_delegation(inode, FMODE_READ)) |
| 1777 | + if (nfs_verifier_is_delegated(dentry)) |
| 1679 | 1778 | return nfs_lookup_revalidate_delegated(dir, dentry, inode); |
| 1680 | 1779 | |
| 1681 | 1780 | /* NFS only supports OPEN on regular files */ |