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

cifs: Fix DFS cache refresher for DFS links

As per MS-DFSC, when a DFS cache entry is expired and it is a DFS
link, then a new DFS referral must be sent to root server in order to
refresh the expired entry.

This patch ensures that all new DFS referrals for refreshing the cache
are sent to DFS root.

Signed-off-by: Paulo Alcantara (SUSE) <paulo@paulo.ac>
Signed-off-by: Steve French <stfrench@microsoft.com>

authored by

Paulo Alcantara (SUSE) and committed by
Steve French
5072010c f5307104

+151 -25
+9
fs/cifs/cifsproto.h
··· 526 526 const struct nls_table *codepage); 527 527 extern int SMBencrypt(unsigned char *passwd, const unsigned char *c8, 528 528 unsigned char *p24); 529 + 530 + extern int 531 + cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data, 532 + const char *devname, bool is_smb3); 529 533 extern void 530 534 cifs_cleanup_volume_info_contents(struct smb_vol *volume_info); 531 535 532 536 extern struct TCP_Server_Info * 533 537 cifs_find_tcp_session(struct smb_vol *vol); 538 + 539 + extern void cifs_put_smb_ses(struct cifs_ses *ses); 540 + 541 + extern struct cifs_ses * 542 + cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info); 534 543 535 544 void cifs_readdata_release(struct kref *refcount); 536 545 int cifs_async_readv(struct cifs_readdata *rdata);
+14 -8
fs/cifs/connect.c
··· 323 323 static int generic_ip_connect(struct TCP_Server_Info *server); 324 324 static void tlink_rb_insert(struct rb_root *root, struct tcon_link *new_tlink); 325 325 static void cifs_prune_tlinks(struct work_struct *work); 326 - static int cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data, 327 - const char *devname, bool is_smb3); 328 326 static char *extract_hostname(const char *unc); 329 327 330 328 /* ··· 2902 2904 return NULL; 2903 2905 } 2904 2906 2905 - static void 2906 - cifs_put_smb_ses(struct cifs_ses *ses) 2907 + void cifs_put_smb_ses(struct cifs_ses *ses) 2907 2908 { 2908 2909 unsigned int rc, xid; 2909 2910 struct TCP_Server_Info *server = ses->server; ··· 3079 3082 * already got a server reference (server refcount +1). See 3080 3083 * cifs_get_tcon() for refcount explanations. 3081 3084 */ 3082 - static struct cifs_ses * 3085 + struct cifs_ses * 3083 3086 cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb_vol *volume_info) 3084 3087 { 3085 3088 int rc = -ENOMEM; ··· 4386 4389 } 4387 4390 #endif 4388 4391 4389 - static int 4392 + int 4390 4393 cifs_setup_volume_info(struct smb_vol *volume_info, char *mount_data, 4391 4394 const char *devname, bool is_smb3) 4392 4395 { ··· 4540 4543 struct cifs_tcon *tcon = NULL; 4541 4544 struct TCP_Server_Info *server; 4542 4545 char *root_path = NULL, *full_path = NULL; 4543 - char *old_mountdata; 4546 + char *old_mountdata, *origin_mountdata = NULL; 4544 4547 int count; 4545 4548 4546 4549 rc = mount_get_conns(vol, cifs_sb, &xid, &server, &ses, &tcon); ··· 4596 4599 4597 4600 if (cifs_sb->mountdata == NULL) { 4598 4601 rc = -ENOENT; 4602 + goto error; 4603 + } 4604 + 4605 + /* Save DFS root volume information for DFS refresh worker */ 4606 + origin_mountdata = kstrndup(cifs_sb->mountdata, 4607 + strlen(cifs_sb->mountdata), GFP_KERNEL); 4608 + if (!origin_mountdata) { 4609 + rc = -ENOMEM; 4599 4610 goto error; 4600 4611 } 4601 4612 ··· 4715 4710 } 4716 4711 spin_unlock(&cifs_tcp_ses_lock); 4717 4712 4718 - rc = dfs_cache_add_vol(vol, cifs_sb->origin_fullpath); 4713 + rc = dfs_cache_add_vol(origin_mountdata, vol, cifs_sb->origin_fullpath); 4719 4714 if (rc) { 4720 4715 kfree(cifs_sb->origin_fullpath); 4721 4716 goto error; ··· 4733 4728 error: 4734 4729 kfree(full_path); 4735 4730 kfree(root_path); 4731 + kfree(origin_mountdata); 4736 4732 mount_put_conns(cifs_sb, xid, server, ses, tcon); 4737 4733 return rc; 4738 4734 }
+125 -15
fs/cifs/dfs_cache.c
··· 2 2 /* 3 3 * DFS referral cache routines 4 4 * 5 - * Copyright (c) 2018 Paulo Alcantara <palcantara@suse.de> 5 + * Copyright (c) 2018-2019 Paulo Alcantara <palcantara@suse.de> 6 6 */ 7 7 8 8 #include <linux/rcupdate.h> ··· 52 52 struct dfs_cache_vol_info { 53 53 char *vi_fullpath; 54 54 struct smb_vol vi_vol; 55 + char *vi_mntdata; 55 56 struct list_head vi_list; 56 57 }; 57 58 ··· 530 529 { 531 530 list_del(&vi->vi_list); 532 531 kfree(vi->vi_fullpath); 532 + kfree(vi->vi_mntdata); 533 533 cifs_cleanup_volume_info_contents(&vi->vi_vol); 534 534 kfree(vi); 535 535 } ··· 1141 1139 * dfs_cache_add_vol - add a cifs volume during mount() that will be handled by 1142 1140 * DFS cache refresh worker. 1143 1141 * 1142 + * @mntdata: mount data. 1144 1143 * @vol: cifs volume. 1145 1144 * @fullpath: origin full path. 1146 1145 * 1147 1146 * Return zero if volume was set up correctly, otherwise non-zero. 1148 1147 */ 1149 - int dfs_cache_add_vol(struct smb_vol *vol, const char *fullpath) 1148 + int dfs_cache_add_vol(char *mntdata, struct smb_vol *vol, const char *fullpath) 1150 1149 { 1151 1150 int rc; 1152 1151 struct dfs_cache_vol_info *vi; 1153 1152 1154 - if (!vol || !fullpath) 1153 + if (!vol || !fullpath || !mntdata) 1155 1154 return -EINVAL; 1156 1155 1157 1156 cifs_dbg(FYI, "%s: fullpath: %s\n", __func__, fullpath); ··· 1170 1167 rc = dup_vol(vol, &vi->vi_vol); 1171 1168 if (rc) 1172 1169 goto err_free_fullpath; 1170 + 1171 + vi->vi_mntdata = mntdata; 1173 1172 1174 1173 mutex_lock(&dfs_cache.dc_lock); 1175 1174 list_add_tail(&vi->vi_list, &dfs_cache.dc_vol_list); ··· 1280 1275 spin_unlock(&cifs_tcp_ses_lock); 1281 1276 } 1282 1277 1278 + static inline bool is_dfs_link(const char *path) 1279 + { 1280 + char *s; 1281 + 1282 + s = strchr(path + 1, '\\'); 1283 + if (!s) 1284 + return false; 1285 + return !!strchr(s + 1, '\\'); 1286 + } 1287 + 1288 + static inline char *get_dfs_root(const char *path) 1289 + { 1290 + char *s, *npath; 1291 + 1292 + s = strchr(path + 1, '\\'); 1293 + if (!s) 1294 + return ERR_PTR(-EINVAL); 1295 + 1296 + s = strchr(s + 1, '\\'); 1297 + if (!s) 1298 + return ERR_PTR(-EINVAL); 1299 + 1300 + npath = kstrndup(path, s - path, GFP_KERNEL); 1301 + if (!npath) 1302 + return ERR_PTR(-ENOMEM); 1303 + 1304 + return npath; 1305 + } 1306 + 1307 + /* Find root SMB session out of a DFS link path */ 1308 + static struct cifs_ses *find_root_ses(struct dfs_cache_vol_info *vi, 1309 + struct cifs_tcon *tcon, const char *path) 1310 + { 1311 + char *rpath; 1312 + int rc; 1313 + struct dfs_info3_param ref = {0}; 1314 + char *mdata = NULL, *devname = NULL; 1315 + bool is_smb3 = tcon->ses->server->vals->header_preamble_size == 0; 1316 + struct TCP_Server_Info *server; 1317 + struct cifs_ses *ses; 1318 + struct smb_vol vol; 1319 + 1320 + rpath = get_dfs_root(path); 1321 + if (IS_ERR(rpath)) 1322 + return ERR_CAST(rpath); 1323 + 1324 + memset(&vol, 0, sizeof(vol)); 1325 + 1326 + rc = dfs_cache_noreq_find(rpath, &ref, NULL); 1327 + if (rc) { 1328 + ses = ERR_PTR(rc); 1329 + goto out; 1330 + } 1331 + 1332 + mdata = cifs_compose_mount_options(vi->vi_mntdata, rpath, &ref, 1333 + &devname); 1334 + free_dfs_info_param(&ref); 1335 + 1336 + if (IS_ERR(mdata)) { 1337 + ses = ERR_CAST(mdata); 1338 + mdata = NULL; 1339 + goto out; 1340 + } 1341 + 1342 + rc = cifs_setup_volume_info(&vol, mdata, devname, is_smb3); 1343 + kfree(devname); 1344 + 1345 + if (rc) { 1346 + ses = ERR_PTR(rc); 1347 + goto out; 1348 + } 1349 + 1350 + server = cifs_find_tcp_session(&vol); 1351 + if (IS_ERR_OR_NULL(server)) { 1352 + ses = ERR_PTR(-EHOSTDOWN); 1353 + goto out; 1354 + } 1355 + if (server->tcpStatus != CifsGood) { 1356 + cifs_put_tcp_session(server, 0); 1357 + ses = ERR_PTR(-EHOSTDOWN); 1358 + goto out; 1359 + } 1360 + 1361 + ses = cifs_get_smb_ses(server, &vol); 1362 + 1363 + out: 1364 + cifs_cleanup_volume_info_contents(&vol); 1365 + kfree(mdata); 1366 + kfree(rpath); 1367 + 1368 + return ses; 1369 + } 1370 + 1283 1371 /* Refresh DFS cache entry from a given tcon */ 1284 - static void do_refresh_tcon(struct dfs_cache *dc, struct cifs_tcon *tcon) 1372 + static void do_refresh_tcon(struct dfs_cache *dc, struct dfs_cache_vol_info *vi, 1373 + struct cifs_tcon *tcon) 1285 1374 { 1286 1375 int rc = 0; 1287 1376 unsigned int xid; ··· 1384 1285 struct dfs_cache_entry *ce; 1385 1286 struct dfs_info3_param *refs = NULL; 1386 1287 int numrefs = 0; 1288 + struct cifs_ses *root_ses = NULL, *ses; 1387 1289 1388 1290 xid = get_xid(); 1389 1291 ··· 1406 1306 if (!cache_entry_expired(ce)) 1407 1307 goto out; 1408 1308 1409 - if (unlikely(!tcon->ses->server->ops->get_dfs_refer)) { 1309 + /* If it's a DFS Link, then use root SMB session for refreshing it */ 1310 + if (is_dfs_link(npath)) { 1311 + ses = root_ses = find_root_ses(vi, tcon, npath); 1312 + if (IS_ERR(ses)) { 1313 + rc = PTR_ERR(ses); 1314 + root_ses = NULL; 1315 + goto out; 1316 + } 1317 + } else { 1318 + ses = tcon->ses; 1319 + } 1320 + 1321 + if (unlikely(!ses->server->ops->get_dfs_refer)) { 1410 1322 rc = -EOPNOTSUPP; 1411 1323 } else { 1412 - rc = tcon->ses->server->ops->get_dfs_refer(xid, tcon->ses, path, 1413 - &refs, &numrefs, 1414 - dc->dc_nlsc, 1415 - tcon->remap); 1324 + rc = ses->server->ops->get_dfs_refer(xid, ses, path, &refs, 1325 + &numrefs, dc->dc_nlsc, 1326 + tcon->remap); 1416 1327 if (!rc) { 1417 1328 mutex_lock(&dfs_cache_list_lock); 1418 1329 ce = __update_cache_entry(npath, refs, numrefs); ··· 1434 1323 rc = PTR_ERR(ce); 1435 1324 } 1436 1325 } 1437 - if (rc) 1438 - cifs_dbg(FYI, "%s: failed to update expired entry\n", __func__); 1326 + 1439 1327 out: 1328 + if (root_ses) 1329 + cifs_put_smb_ses(root_ses); 1330 + 1440 1331 free_xid(xid); 1441 1332 free_normalized_path(path, npath); 1442 1333 } ··· 1446 1333 /* 1447 1334 * Worker that will refresh DFS cache based on lowest TTL value from a DFS 1448 1335 * referral. 1449 - * 1450 - * FIXME: ensure that all requests are sent to DFS root for refreshing the 1451 - * cache. 1452 1336 */ 1453 1337 static void refresh_cache_worker(struct work_struct *work) 1454 1338 { ··· 1466 1356 goto next; 1467 1357 get_tcons(server, &list); 1468 1358 list_for_each_entry_safe(tcon, ntcon, &list, ulist) { 1469 - do_refresh_tcon(dc, tcon); 1359 + do_refresh_tcon(dc, vi, tcon); 1470 1360 list_del_init(&tcon->ulist); 1471 1361 cifs_put_tcon(tcon); 1472 1362 }
+3 -2
fs/cifs/dfs_cache.h
··· 2 2 /* 3 3 * DFS referral cache routines 4 4 * 5 - * Copyright (c) 2018 Paulo Alcantara <palcantara@suse.de> 5 + * Copyright (c) 2018-2019 Paulo Alcantara <palcantara@suse.de> 6 6 */ 7 7 8 8 #ifndef _CIFS_DFS_CACHE_H ··· 43 43 extern int dfs_cache_get_tgt_referral(const char *path, 44 44 const struct dfs_cache_tgt_iterator *it, 45 45 struct dfs_info3_param *ref); 46 - extern int dfs_cache_add_vol(struct smb_vol *vol, const char *fullpath); 46 + extern int dfs_cache_add_vol(char *mntdata, struct smb_vol *vol, 47 + const char *fullpath); 47 48 extern int dfs_cache_update_vol(const char *fullpath, 48 49 struct TCP_Server_Info *server); 49 50 extern void dfs_cache_del_vol(const char *fullpath);