|
21 | 21 | #include "cifsfs.h" |
22 | 22 | #ifdef CONFIG_CIFS_DFS_UPCALL |
23 | 23 | #include "dns_resolve.h" |
| 24 | +#include "dfs_cache.h" |
24 | 25 | #endif |
25 | 26 | #include "fs_context.h" |
26 | 27 | #include "cached_dir.h" |
@@ -1198,4 +1199,70 @@ int cifs_update_super_prepath(struct cifs_sb_info *cifs_sb, char *prefix) |
1198 | 1199 | cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH; |
1199 | 1200 | return 0; |
1200 | 1201 | } |
| 1202 | + |
| 1203 | +/* |
| 1204 | + * Handle weird Windows SMB server behaviour. It responds with |
| 1205 | + * STATUS_OBJECT_NAME_INVALID code to SMB2 QUERY_INFO request for |
| 1206 | + * "\<server>\<dfsname>\<linkpath>" DFS reference, where <dfsname> contains |
| 1207 | + * non-ASCII unicode symbols. |
| 1208 | + */ |
| 1209 | +int cifs_inval_name_dfs_link_error(const unsigned int xid, |
| 1210 | + struct cifs_tcon *tcon, |
| 1211 | + struct cifs_sb_info *cifs_sb, |
| 1212 | + const char *full_path, |
| 1213 | + bool *islink) |
| 1214 | +{ |
| 1215 | + struct cifs_ses *ses = tcon->ses; |
| 1216 | + size_t len; |
| 1217 | + char *path; |
| 1218 | + char *ref_path; |
| 1219 | + |
| 1220 | + *islink = false; |
| 1221 | + |
| 1222 | + /* |
| 1223 | + * Fast path - skip check when @full_path doesn't have a prefix path to |
| 1224 | + * look up or tcon is not DFS. |
| 1225 | + */ |
| 1226 | + if (strlen(full_path) < 2 || !cifs_sb || |
| 1227 | + (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_DFS) || |
| 1228 | + !is_tcon_dfs(tcon) || !ses->server->origin_fullpath) |
| 1229 | + return 0; |
| 1230 | + |
| 1231 | + /* |
| 1232 | + * Slow path - tcon is DFS and @full_path has prefix path, so attempt |
| 1233 | + * to get a referral to figure out whether it is an DFS link. |
| 1234 | + */ |
| 1235 | + len = strnlen(tcon->tree_name, MAX_TREE_SIZE + 1) + strlen(full_path) + 1; |
| 1236 | + path = kmalloc(len, GFP_KERNEL); |
| 1237 | + if (!path) |
| 1238 | + return -ENOMEM; |
| 1239 | + |
| 1240 | + scnprintf(path, len, "%s%s", tcon->tree_name, full_path); |
| 1241 | + ref_path = dfs_cache_canonical_path(path + 1, cifs_sb->local_nls, |
| 1242 | + cifs_remap(cifs_sb)); |
| 1243 | + kfree(path); |
| 1244 | + |
| 1245 | + if (IS_ERR(ref_path)) { |
| 1246 | + if (PTR_ERR(ref_path) != -EINVAL) |
| 1247 | + return PTR_ERR(ref_path); |
| 1248 | + } else { |
| 1249 | + struct dfs_info3_param *refs = NULL; |
| 1250 | + int num_refs = 0; |
| 1251 | + |
| 1252 | + /* |
| 1253 | + * XXX: we are not using dfs_cache_find() here because we might |
| 1254 | + * end filling all the DFS cache and thus potentially |
| 1255 | + * removing cached DFS targets that the client would eventually |
| 1256 | + * need during failover. |
| 1257 | + */ |
| 1258 | + if (ses->server->ops->get_dfs_refer && |
| 1259 | + !ses->server->ops->get_dfs_refer(xid, ses, ref_path, &refs, |
| 1260 | + &num_refs, cifs_sb->local_nls, |
| 1261 | + cifs_remap(cifs_sb))) |
| 1262 | + *islink = refs[0].server_type == DFS_TYPE_LINK; |
| 1263 | + free_dfs_info_array(refs, num_refs); |
| 1264 | + kfree(ref_path); |
| 1265 | + } |
| 1266 | + return 0; |
| 1267 | +} |
1201 | 1268 | #endif |
0 commit comments