Skip to content

Commit b623661

Browse files
Paulo Alcantarasmfrench
authored andcommitted
cifs: support share failover when remounting
When remouting a DFS share, force a new DFS referral of the path and if the currently cached targets do not match any of the new targets or there was no cached targets, then mark it for reconnect. For example: $ mount //dom/dfs/link /mnt -o username=foo,password=bar $ ls /mnt oldfile.txt change target share of 'link' in server settings $ mount /mnt -o remount,username=foo,password=bar $ ls /mnt newfile.txt Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz> Signed-off-by: Steve French <stfrench@microsoft.com>
1 parent 2485bd7 commit b623661

4 files changed

Lines changed: 203 additions & 40 deletions

File tree

fs/cifs/connect.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ cifs_reconnect(struct TCP_Server_Info *server)
220220
#ifdef CONFIG_CIFS_DFS_UPCALL
221221
struct super_block *sb = NULL;
222222
struct cifs_sb_info *cifs_sb = NULL;
223-
struct dfs_cache_tgt_list tgt_list = {0};
223+
struct dfs_cache_tgt_list tgt_list = DFS_CACHE_TGT_LIST_INIT(tgt_list);
224224
struct dfs_cache_tgt_iterator *tgt_it = NULL;
225225
#endif
226226

@@ -3130,7 +3130,7 @@ static int do_dfs_failover(const char *path, const char *full_path, struct cifs_
31303130
{
31313131
int rc;
31323132
char *npath = NULL;
3133-
struct dfs_cache_tgt_list tgt_list = {0};
3133+
struct dfs_cache_tgt_list tgt_list = DFS_CACHE_TGT_LIST_INIT(tgt_list);
31343134
struct dfs_cache_tgt_iterator *tgt_it = NULL;
31353135
struct smb3_fs_context tmp_ctx = {NULL};
31363136

fs/cifs/dfs_cache.c

Lines changed: 191 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "cifs_debug.h"
2020
#include "cifs_unicode.h"
2121
#include "smb2glob.h"
22+
#include "dns_resolve.h"
2223

2324
#include "dfs_cache.h"
2425

@@ -911,6 +912,7 @@ static int get_targets(struct cache_entry *ce, struct dfs_cache_tgt_list *tl)
911912

912913
err_free_it:
913914
list_for_each_entry_safe(it, nit, head, it_list) {
915+
list_del(&it->it_list);
914916
kfree(it->it_name);
915917
kfree(it);
916918
}
@@ -1293,6 +1295,194 @@ int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it,
12931295
return 0;
12941296
}
12951297

1298+
static bool target_share_equal(struct TCP_Server_Info *server, const char *s1, const char *s2)
1299+
{
1300+
char unc[sizeof("\\\\") + SERVER_NAME_LENGTH] = {0};
1301+
const char *host;
1302+
size_t hostlen;
1303+
char *ip = NULL;
1304+
struct sockaddr sa;
1305+
bool match;
1306+
int rc;
1307+
1308+
if (strcasecmp(s1, s2))
1309+
return false;
1310+
1311+
/*
1312+
* Resolve share's hostname and check if server address matches. Otherwise just ignore it
1313+
* as we could not have upcall to resolve hostname or failed to convert ip address.
1314+
*/
1315+
match = true;
1316+
extract_unc_hostname(s1, &host, &hostlen);
1317+
scnprintf(unc, sizeof(unc), "\\\\%.*s", (int)hostlen, host);
1318+
1319+
rc = dns_resolve_server_name_to_ip(unc, &ip, NULL);
1320+
if (rc < 0) {
1321+
cifs_dbg(FYI, "%s: could not resolve %.*s. assuming server address matches.\n",
1322+
__func__, (int)hostlen, host);
1323+
return true;
1324+
}
1325+
1326+
if (!cifs_convert_address(&sa, ip, strlen(ip))) {
1327+
cifs_dbg(VFS, "%s: failed to convert address \'%s\'. skip address matching.\n",
1328+
__func__, ip);
1329+
} else {
1330+
mutex_lock(&server->srv_mutex);
1331+
match = cifs_match_ipaddr((struct sockaddr *)&server->dstaddr, &sa);
1332+
mutex_unlock(&server->srv_mutex);
1333+
}
1334+
1335+
kfree(ip);
1336+
return match;
1337+
}
1338+
1339+
/*
1340+
* Mark dfs tcon for reconnecting when the currently connected tcon does not match any of the new
1341+
* target shares in @refs.
1342+
*/
1343+
static void mark_for_reconnect_if_needed(struct cifs_tcon *tcon, struct dfs_cache_tgt_list *tl,
1344+
const struct dfs_info3_param *refs, int numrefs)
1345+
{
1346+
struct dfs_cache_tgt_iterator *it;
1347+
int i;
1348+
1349+
for (it = dfs_cache_get_tgt_iterator(tl); it; it = dfs_cache_get_next_tgt(tl, it)) {
1350+
for (i = 0; i < numrefs; i++) {
1351+
if (target_share_equal(tcon->ses->server, dfs_cache_get_tgt_name(it),
1352+
refs[i].node_name))
1353+
return;
1354+
}
1355+
}
1356+
1357+
cifs_dbg(FYI, "%s: no cached or matched targets. mark dfs share for reconnect.\n", __func__);
1358+
for (i = 0; i < tcon->ses->chan_count; i++) {
1359+
spin_lock(&GlobalMid_Lock);
1360+
if (tcon->ses->chans[i].server->tcpStatus != CifsExiting)
1361+
tcon->ses->chans[i].server->tcpStatus = CifsNeedReconnect;
1362+
spin_unlock(&GlobalMid_Lock);
1363+
}
1364+
}
1365+
1366+
/* Refresh dfs referral of tcon and mark it for reconnect if needed */
1367+
static int refresh_tcon(struct cifs_ses **sessions, struct cifs_tcon *tcon, bool force_refresh)
1368+
{
1369+
const char *path = tcon->dfs_path + 1;
1370+
struct cifs_ses *ses;
1371+
struct cache_entry *ce;
1372+
struct dfs_info3_param *refs = NULL;
1373+
int numrefs = 0;
1374+
bool needs_refresh = false;
1375+
struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
1376+
int rc = 0;
1377+
unsigned int xid;
1378+
1379+
ses = find_ipc_from_server_path(sessions, path);
1380+
if (IS_ERR(ses)) {
1381+
cifs_dbg(FYI, "%s: could not find ipc session\n", __func__);
1382+
return PTR_ERR(ses);
1383+
}
1384+
1385+
down_read(&htable_rw_lock);
1386+
ce = lookup_cache_entry(path);
1387+
needs_refresh = force_refresh || IS_ERR(ce) || cache_entry_expired(ce);
1388+
if (!IS_ERR(ce)) {
1389+
rc = get_targets(ce, &tl);
1390+
if (rc)
1391+
cifs_dbg(FYI, "%s: could not get dfs targets: %d\n", __func__, rc);
1392+
}
1393+
up_read(&htable_rw_lock);
1394+
1395+
if (!needs_refresh) {
1396+
rc = 0;
1397+
goto out;
1398+
}
1399+
1400+
xid = get_xid();
1401+
rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
1402+
free_xid(xid);
1403+
1404+
/* Create or update a cache entry with the new referral */
1405+
if (!rc) {
1406+
dump_refs(refs, numrefs);
1407+
1408+
down_write(&htable_rw_lock);
1409+
ce = lookup_cache_entry(path);
1410+
if (IS_ERR(ce))
1411+
add_cache_entry_locked(refs, numrefs);
1412+
else if (force_refresh || cache_entry_expired(ce))
1413+
update_cache_entry_locked(ce, refs, numrefs);
1414+
up_write(&htable_rw_lock);
1415+
1416+
mark_for_reconnect_if_needed(tcon, &tl, refs, numrefs);
1417+
}
1418+
1419+
out:
1420+
dfs_cache_free_tgts(&tl);
1421+
free_dfs_info_array(refs, numrefs);
1422+
return rc;
1423+
}
1424+
1425+
/**
1426+
* dfs_cache_remount_fs - remount a DFS share
1427+
*
1428+
* Reconfigure dfs mount by forcing a new DFS referral and if the currently cached targets do not
1429+
* match any of the new targets, mark it for reconnect.
1430+
*
1431+
* @cifs_sb: cifs superblock.
1432+
*
1433+
* Return zero if remounted, otherwise non-zero.
1434+
*/
1435+
int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb)
1436+
{
1437+
struct cifs_tcon *tcon;
1438+
struct mount_group *mg;
1439+
struct cifs_ses *sessions[CACHE_MAX_ENTRIES + 1] = {NULL};
1440+
int rc;
1441+
1442+
if (!cifs_sb || !cifs_sb->master_tlink)
1443+
return -EINVAL;
1444+
1445+
tcon = cifs_sb_master_tcon(cifs_sb);
1446+
if (!tcon->dfs_path) {
1447+
cifs_dbg(FYI, "%s: not a dfs tcon\n", __func__);
1448+
return 0;
1449+
}
1450+
1451+
if (uuid_is_null(&cifs_sb->dfs_mount_id)) {
1452+
cifs_dbg(FYI, "%s: tcon has no dfs mount group id\n", __func__);
1453+
return -EINVAL;
1454+
}
1455+
1456+
mutex_lock(&mount_group_list_lock);
1457+
mg = find_mount_group_locked(&cifs_sb->dfs_mount_id);
1458+
if (IS_ERR(mg)) {
1459+
mutex_unlock(&mount_group_list_lock);
1460+
cifs_dbg(FYI, "%s: tcon has ipc session to refresh referral\n", __func__);
1461+
return PTR_ERR(mg);
1462+
}
1463+
kref_get(&mg->refcount);
1464+
mutex_unlock(&mount_group_list_lock);
1465+
1466+
spin_lock(&mg->lock);
1467+
memcpy(&sessions, mg->sessions, mg->num_sessions * sizeof(mg->sessions[0]));
1468+
spin_unlock(&mg->lock);
1469+
1470+
/*
1471+
* After reconnecting to a different server, unique ids won't match anymore, so we disable
1472+
* serverino. This prevents dentry revalidation to think the dentry are stale (ESTALE).
1473+
*/
1474+
cifs_autodisable_serverino(cifs_sb);
1475+
/*
1476+
* Force the use of prefix path to support failover on DFS paths that resolve to targets
1477+
* that have different prefix paths.
1478+
*/
1479+
cifs_sb->mnt_cifs_flags |= CIFS_MOUNT_USE_PREFIX_PATH;
1480+
rc = refresh_tcon(sessions, tcon, true);
1481+
1482+
kref_put(&mg->refcount, mount_group_release);
1483+
return rc;
1484+
}
1485+
12961486
/*
12971487
* Refresh all active dfs mounts regardless of whether they are in cache or not.
12981488
* (cache can be cleared)
@@ -1303,7 +1493,6 @@ static void refresh_mounts(struct cifs_ses **sessions)
13031493
struct cifs_ses *ses;
13041494
struct cifs_tcon *tcon, *ntcon;
13051495
struct list_head tcons;
1306-
unsigned int xid;
13071496

13081497
INIT_LIST_HEAD(&tcons);
13091498

@@ -1321,44 +1510,8 @@ static void refresh_mounts(struct cifs_ses **sessions)
13211510
spin_unlock(&cifs_tcp_ses_lock);
13221511

13231512
list_for_each_entry_safe(tcon, ntcon, &tcons, ulist) {
1324-
const char *path = tcon->dfs_path + 1;
1325-
struct cache_entry *ce;
1326-
struct dfs_info3_param *refs = NULL;
1327-
int numrefs = 0;
1328-
bool needs_refresh = false;
1329-
int rc = 0;
1330-
13311513
list_del_init(&tcon->ulist);
1332-
1333-
ses = find_ipc_from_server_path(sessions, path);
1334-
if (IS_ERR(ses))
1335-
goto next_tcon;
1336-
1337-
down_read(&htable_rw_lock);
1338-
ce = lookup_cache_entry(path);
1339-
needs_refresh = IS_ERR(ce) || cache_entry_expired(ce);
1340-
up_read(&htable_rw_lock);
1341-
1342-
if (!needs_refresh)
1343-
goto next_tcon;
1344-
1345-
xid = get_xid();
1346-
rc = get_dfs_referral(xid, ses, path, &refs, &numrefs);
1347-
free_xid(xid);
1348-
1349-
/* Create or update a cache entry with the new referral */
1350-
if (!rc) {
1351-
down_write(&htable_rw_lock);
1352-
ce = lookup_cache_entry(path);
1353-
if (IS_ERR(ce))
1354-
add_cache_entry_locked(refs, numrefs);
1355-
else if (cache_entry_expired(ce))
1356-
update_cache_entry_locked(ce, refs, numrefs);
1357-
up_write(&htable_rw_lock);
1358-
}
1359-
1360-
next_tcon:
1361-
free_dfs_info_array(refs, numrefs);
1514+
refresh_tcon(sessions, tcon, false);
13621515
cifs_put_tcon(tcon);
13631516
}
13641517
}

fs/cifs/dfs_cache.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
#include <linux/uuid.h>
1414
#include "cifsglob.h"
1515

16+
#define DFS_CACHE_TGT_LIST_INIT(var) { .tl_numtgts = 0, .tl_list = LIST_HEAD_INIT((var).tl_list), }
17+
1618
struct dfs_cache_tgt_list {
1719
int tl_numtgts;
1820
struct list_head tl_list;
@@ -44,6 +46,7 @@ int dfs_cache_get_tgt_share(char *path, const struct dfs_cache_tgt_iterator *it,
4446
void dfs_cache_put_refsrv_sessions(const uuid_t *mount_id);
4547
void dfs_cache_add_refsrv_session(const uuid_t *mount_id, struct cifs_ses *ses);
4648
char *dfs_cache_canonical_path(const char *path, const struct nls_table *cp, int remap);
49+
int dfs_cache_remount_fs(struct cifs_sb_info *cifs_sb);
4750

4851
static inline struct dfs_cache_tgt_iterator *
4952
dfs_cache_get_next_tgt(struct dfs_cache_tgt_list *tl,

fs/cifs/fs_context.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#include <linux/magic.h>
1414
#include <linux/security.h>
1515
#include <net/net_namespace.h>
16+
#ifdef CONFIG_CIFS_DFS_UPCALL
17+
#include "dfs_cache.h"
18+
#endif
1619
*/
1720

1821
#include <linux/ctype.h>
@@ -779,6 +782,10 @@ static int smb3_reconfigure(struct fs_context *fc)
779782
smb3_cleanup_fs_context_contents(cifs_sb->ctx);
780783
rc = smb3_fs_context_dup(cifs_sb->ctx, ctx);
781784
smb3_update_mnt_flags(cifs_sb);
785+
#ifdef CONFIG_CIFS_DFS_UPCALL
786+
if (!rc)
787+
rc = dfs_cache_remount_fs(cifs_sb);
788+
#endif
782789

783790
return rc;
784791
}

0 commit comments

Comments
 (0)