cifs: Optimize readdir on reparse points

When listing a directory with thounsands of files and most of them are
reparse points, we simply marked all those dentries for revalidation
and then sending additional (compounded) create/getinfo/close requests
for each of them.

Instead, upon receiving a response from an SMB2_QUERY_DIRECTORY
(FileIdFullDirectoryInformation) command, the directory entries that
have a file attribute of FILE_ATTRIBUTE_REPARSE_POINT will contain an
EaSize field with a reparse tag in it, so we parse it and mark the
dentry for revalidation only if it is a DFS or a symlink.

Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
Reviewed-by: Pavel Shilovsky <pshilov@microsoft.com>
Signed-off-by: Steve French <stfrench@microsoft.com>

authored by Paulo Alcantara (SUSE) and committed by Steve French 046aca3c 7935799e

Changed files
+55 -9
fs
+1
fs/cifs/cifsglob.h
··· 1693 1693 struct timespec64 cf_atime; 1694 1694 struct timespec64 cf_mtime; 1695 1695 struct timespec64 cf_ctime; 1696 + u32 cf_cifstag; 1696 1697 }; 1697 1698 1698 1699 static inline void free_dfs_info_param(struct dfs_info3_param *param)
+54 -9
fs/cifs/readdir.c
··· 139 139 dput(dentry); 140 140 } 141 141 142 + static bool reparse_file_needs_reval(const struct cifs_fattr *fattr) 143 + { 144 + if (!(fattr->cf_cifsattrs & ATTR_REPARSE)) 145 + return false; 146 + /* 147 + * The DFS tags should be only intepreted by server side as per 148 + * MS-FSCC 2.1.2.1, but let's include them anyway. 149 + * 150 + * Besides, if cf_cifstag is unset (0), then we still need it to be 151 + * revalidated to know exactly what reparse point it is. 152 + */ 153 + switch (fattr->cf_cifstag) { 154 + case IO_REPARSE_TAG_DFS: 155 + case IO_REPARSE_TAG_DFSR: 156 + case IO_REPARSE_TAG_SYMLINK: 157 + case IO_REPARSE_TAG_NFS: 158 + case 0: 159 + return true; 160 + } 161 + return false; 162 + } 163 + 142 164 static void 143 165 cifs_fill_common_info(struct cifs_fattr *fattr, struct cifs_sb_info *cifs_sb) 144 166 { ··· 180 158 * is a symbolic link, DFS referral or a reparse point with a direct 181 159 * access like junctions, deduplicated files, NFS symlinks. 182 160 */ 183 - if (fattr->cf_cifsattrs & ATTR_REPARSE) 161 + if (reparse_file_needs_reval(fattr)) 184 162 fattr->cf_flags |= CIFS_FATTR_NEED_REVAL; 185 163 186 164 /* non-unix readdir doesn't provide nlink */ ··· 216 194 } 217 195 } 218 196 197 + static void __dir_info_to_fattr(struct cifs_fattr *fattr, const void *info) 198 + { 199 + const FILE_DIRECTORY_INFO *fi = info; 200 + 201 + memset(fattr, 0, sizeof(*fattr)); 202 + fattr->cf_cifsattrs = le32_to_cpu(fi->ExtFileAttributes); 203 + fattr->cf_eof = le64_to_cpu(fi->EndOfFile); 204 + fattr->cf_bytes = le64_to_cpu(fi->AllocationSize); 205 + fattr->cf_createtime = le64_to_cpu(fi->CreationTime); 206 + fattr->cf_atime = cifs_NTtimeToUnix(fi->LastAccessTime); 207 + fattr->cf_ctime = cifs_NTtimeToUnix(fi->ChangeTime); 208 + fattr->cf_mtime = cifs_NTtimeToUnix(fi->LastWriteTime); 209 + } 210 + 219 211 void 220 212 cifs_dir_info_to_fattr(struct cifs_fattr *fattr, FILE_DIRECTORY_INFO *info, 221 213 struct cifs_sb_info *cifs_sb) 222 214 { 223 - memset(fattr, 0, sizeof(*fattr)); 224 - fattr->cf_cifsattrs = le32_to_cpu(info->ExtFileAttributes); 225 - fattr->cf_eof = le64_to_cpu(info->EndOfFile); 226 - fattr->cf_bytes = le64_to_cpu(info->AllocationSize); 227 - fattr->cf_createtime = le64_to_cpu(info->CreationTime); 228 - fattr->cf_atime = cifs_NTtimeToUnix(info->LastAccessTime); 229 - fattr->cf_ctime = cifs_NTtimeToUnix(info->ChangeTime); 230 - fattr->cf_mtime = cifs_NTtimeToUnix(info->LastWriteTime); 215 + __dir_info_to_fattr(fattr, info); 216 + cifs_fill_common_info(fattr, cifs_sb); 217 + } 231 218 219 + static void cifs_fulldir_info_to_fattr(struct cifs_fattr *fattr, 220 + SEARCH_ID_FULL_DIR_INFO *info, 221 + struct cifs_sb_info *cifs_sb) 222 + { 223 + __dir_info_to_fattr(fattr, info); 224 + 225 + /* See MS-FSCC 2.4.18 FileIdFullDirectoryInformation */ 226 + if (fattr->cf_cifsattrs & ATTR_REPARSE) 227 + fattr->cf_cifstag = le32_to_cpu(info->EaSize); 232 228 cifs_fill_common_info(fattr, cifs_sb); 233 229 } 234 230 ··· 794 754 cifs_std_info_to_fattr(&fattr, 795 755 (FIND_FILE_STANDARD_INFO *)find_entry, 796 756 cifs_sb); 757 + break; 758 + case SMB_FIND_FILE_ID_FULL_DIR_INFO: 759 + cifs_fulldir_info_to_fattr(&fattr, 760 + (SEARCH_ID_FULL_DIR_INFO *)find_entry, 761 + cifs_sb); 797 762 break; 798 763 default: 799 764 cifs_dir_info_to_fattr(&fattr,