cifs: split out dfs code from cifs_reconnect()

Make two separate functions that handle dfs and non-dfs reconnect
logics since cifs_reconnect() became way too complex to handle both.
While at it, add some documentation.

Signed-off-by: Paulo Alcantara (SUSE) <pc@cjr.nz>
Reviewed-by: Shyam Prasad N <sprasad@microsoft.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
Paulo Alcantara 2021-10-14 17:49:54 -03:00 committed by Steve French
parent ae0abb4dac
commit bbcce36804

View File

@ -148,57 +148,6 @@ static void cifs_resolve_server(struct work_struct *work)
mutex_unlock(&server->srv_mutex);
}
#ifdef CONFIG_CIFS_DFS_UPCALL
/* These functions must be called with server->srv_mutex held */
static void reconn_set_next_dfs_target(struct TCP_Server_Info *server,
struct cifs_sb_info *cifs_sb,
struct dfs_cache_tgt_list *tgt_list,
struct dfs_cache_tgt_iterator **tgt_it)
{
const char *name;
int rc;
if (!cifs_sb || !cifs_sb->origin_fullpath)
return;
if (!*tgt_it) {
*tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
} else {
*tgt_it = dfs_cache_get_next_tgt(tgt_list, *tgt_it);
if (!*tgt_it)
*tgt_it = dfs_cache_get_tgt_iterator(tgt_list);
}
cifs_dbg(FYI, "%s: UNC: %s\n", __func__, cifs_sb->origin_fullpath);
name = dfs_cache_get_tgt_name(*tgt_it);
kfree(server->hostname);
server->hostname = extract_hostname(name);
if (IS_ERR(server->hostname)) {
cifs_dbg(FYI,
"%s: failed to extract hostname from target: %ld\n",
__func__, PTR_ERR(server->hostname));
return;
}
rc = reconn_set_ipaddr_from_hostname(server);
if (rc) {
cifs_dbg(FYI, "%s: failed to resolve hostname: %d\n",
__func__, rc);
}
}
static inline int reconn_setup_dfs_targets(struct cifs_sb_info *cifs_sb,
struct dfs_cache_tgt_list *tl)
{
if (!cifs_sb->origin_fullpath)
return -EOPNOTSUPP;
return dfs_cache_noreq_find(cifs_sb->origin_fullpath + 1, NULL, tl);
}
#endif
/**
* Mark all sessions and tcons for reconnect.
*
@ -278,6 +227,21 @@ static void cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server
}
}
static bool cifs_tcp_ses_needs_reconnect(struct TCP_Server_Info *server, int num_targets)
{
spin_lock(&GlobalMid_Lock);
server->nr_targets = num_targets;
if (server->tcpStatus == CifsExiting) {
/* the demux thread will exit normally next time through the loop */
spin_unlock(&GlobalMid_Lock);
wake_up(&server->response_q);
return false;
}
server->tcpStatus = CifsNeedReconnect;
spin_unlock(&GlobalMid_Lock);
return true;
}
/*
* cifs tcp session reconnection
*
@ -286,90 +250,23 @@ static void cifs_mark_tcp_ses_conns_for_reconnect(struct TCP_Server_Info *server
* reconnect tcp session
* wake up waiters on reconnection? - (not needed currently)
*/
int
cifs_reconnect(struct TCP_Server_Info *server)
static int __cifs_reconnect(struct TCP_Server_Info *server)
{
int rc = 0;
#ifdef CONFIG_CIFS_DFS_UPCALL
struct super_block *sb = NULL;
struct cifs_sb_info *cifs_sb = NULL;
struct dfs_cache_tgt_list tgt_list = DFS_CACHE_TGT_LIST_INIT(tgt_list);
struct dfs_cache_tgt_iterator *tgt_it = NULL;
#endif
spin_lock(&GlobalMid_Lock);
server->nr_targets = 1;
#ifdef CONFIG_CIFS_DFS_UPCALL
spin_unlock(&GlobalMid_Lock);
sb = cifs_get_tcp_super(server);
if (IS_ERR(sb)) {
rc = PTR_ERR(sb);
cifs_dbg(FYI, "%s: will not do DFS failover: rc = %d\n",
__func__, rc);
sb = NULL;
} else {
cifs_sb = CIFS_SB(sb);
rc = reconn_setup_dfs_targets(cifs_sb, &tgt_list);
if (rc) {
cifs_sb = NULL;
if (rc != -EOPNOTSUPP) {
cifs_server_dbg(VFS, "%s: no target servers for DFS failover\n",
__func__);
}
} else {
server->nr_targets = dfs_cache_get_nr_tgts(&tgt_list);
}
}
cifs_dbg(FYI, "%s: will retry %d target(s)\n", __func__,
server->nr_targets);
spin_lock(&GlobalMid_Lock);
#endif
if (server->tcpStatus == CifsExiting) {
/* the demux thread will exit normally next time through the loop */
spin_unlock(&GlobalMid_Lock);
#ifdef CONFIG_CIFS_DFS_UPCALL
dfs_cache_free_tgts(&tgt_list);
cifs_put_tcp_super(sb);
#endif
wake_up(&server->response_q);
return rc;
} else
server->tcpStatus = CifsNeedReconnect;
spin_unlock(&GlobalMid_Lock);
if (!cifs_tcp_ses_needs_reconnect(server, 1))
return 0;
cifs_mark_tcp_ses_conns_for_reconnect(server);
do {
try_to_freeze();
mutex_lock(&server->srv_mutex);
if (!cifs_swn_set_server_dstaddr(server)) {
#ifdef CONFIG_CIFS_DFS_UPCALL
if (cifs_sb && cifs_sb->origin_fullpath)
/*
* Set up next DFS target server (if any) for reconnect. If DFS
* feature is disabled, then we will retry last server we
* connected to before.
*/
reconn_set_next_dfs_target(server, cifs_sb, &tgt_list, &tgt_it);
else {
#endif
/*
* Resolve the hostname again to make sure that IP address is up-to-date.
*/
/* resolve the hostname again to make sure that IP address is up-to-date */
rc = reconn_set_ipaddr_from_hostname(server);
if (rc) {
cifs_dbg(FYI, "%s: failed to resolve hostname: %d\n",
__func__, rc);
}
#ifdef CONFIG_CIFS_DFS_UPCALL
}
#endif
cifs_dbg(FYI, "%s: reconn_set_ipaddr_from_hostname: rc=%d\n", __func__, rc);
}
if (cifs_rdma_enabled(server))
@ -377,8 +274,8 @@ cifs_reconnect(struct TCP_Server_Info *server)
else
rc = generic_ip_connect(server);
if (rc) {
cifs_dbg(FYI, "reconnect error %d\n", rc);
mutex_unlock(&server->srv_mutex);
cifs_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
msleep(3000);
} else {
atomic_inc(&tcpSesReconnectCount);
@ -392,19 +289,6 @@ cifs_reconnect(struct TCP_Server_Info *server)
}
} while (server->tcpStatus == CifsNeedReconnect);
#ifdef CONFIG_CIFS_DFS_UPCALL
if (tgt_it) {
rc = dfs_cache_noreq_update_tgthint(cifs_sb->origin_fullpath + 1,
tgt_it);
if (rc) {
cifs_server_dbg(VFS, "%s: failed to update DFS target hint: rc = %d\n",
__func__, rc);
}
dfs_cache_free_tgts(&tgt_list);
}
cifs_put_tcp_super(sb);
#endif
if (server->tcpStatus == CifsNeedNegotiate)
mod_delayed_work(cifsiod_wq, &server->echo, 0);
@ -412,6 +296,151 @@ cifs_reconnect(struct TCP_Server_Info *server)
return rc;
}
#ifdef CONFIG_CIFS_DFS_UPCALL
static int reconnect_dfs_server(struct TCP_Server_Info *server, struct cifs_sb_info *cifs_sb)
{
int rc = 0;
const char *refpath = cifs_sb->origin_fullpath + 1;
struct dfs_cache_tgt_list tl = DFS_CACHE_TGT_LIST_INIT(tl);
struct dfs_cache_tgt_iterator *tit = NULL;
int num_targets = 1;
char *hostname;
/*
* Determine the number of dfs targets the referral path in @cifs_sb resolves to.
*
* smb2_reconnect() needs to know how long it should wait based upon the number of dfs
* targets (server->nr_targets). It's also possible that the cached referral was cleared
* through /proc/fs/cifs/dfscache or the target list is empty due to server settings after
* refreshing the referral, so, in this case, default it to 1.
*/
if (!dfs_cache_noreq_find(refpath, NULL, &tl)) {
num_targets = dfs_cache_get_nr_tgts(&tl);
if (!num_targets)
num_targets = 1;
}
if (!cifs_tcp_ses_needs_reconnect(server, num_targets))
return 0;
cifs_mark_tcp_ses_conns_for_reconnect(server);
do {
/* Get next dfs target from target list (if any) */
if (!tit)
tit = dfs_cache_get_tgt_iterator(&tl);
else
tit = dfs_cache_get_next_tgt(&tl, tit);
try_to_freeze();
mutex_lock(&server->srv_mutex);
if (!cifs_swn_set_server_dstaddr(server)) {
/*
* If any dfs target was selected, then update @server with either a
* hostname or an address.
*/
if (tit) {
hostname = extract_hostname(dfs_cache_get_tgt_name(tit));
if (!IS_ERR(hostname)) {
kfree(server->hostname);
server->hostname = hostname;
} else {
cifs_dbg(FYI, "%s: couldn't extract hostname or address from dfs target: %ld\n",
__func__, PTR_ERR(hostname));
cifs_dbg(FYI, "%s: default to last target server: %s\n",
__func__, server->hostname);
}
}
/* resolve the hostname again to make sure that IP address is up-to-date. */
rc = reconn_set_ipaddr_from_hostname(server);
cifs_dbg(FYI, "%s: reconn_set_ipaddr_from_hostname: rc=%d\n", __func__, rc);
}
/* Reconnect the socket */
if (cifs_rdma_enabled(server))
rc = smbd_reconnect(server);
else
rc = generic_ip_connect(server);
if (rc) {
/* Failed to reconnect socket. Retry next dfs target. */
mutex_unlock(&server->srv_mutex);
cifs_dbg(FYI, "%s: reconnect error %d\n", __func__, rc);
msleep(3000);
continue;
}
/*
* Socket was created. Update tcp session status to CifsNeedNegotiate so that a
* process waiting for reconnect will know it needs to re-establish session and tcon
* through the reconnected target server.
*/
atomic_inc(&tcpSesReconnectCount);
set_credits(server, 1);
spin_lock(&GlobalMid_Lock);
if (server->tcpStatus != CifsExiting)
server->tcpStatus = CifsNeedNegotiate;
spin_unlock(&GlobalMid_Lock);
cifs_swn_reset_server_dstaddr(server);
mutex_unlock(&server->srv_mutex);
} while (server->tcpStatus == CifsNeedReconnect);
if (tit)
dfs_cache_noreq_update_tgthint(refpath, tit);
dfs_cache_free_tgts(&tl);
/* Need to set up echo worker again once connection has been established */
if (server->tcpStatus == CifsNeedNegotiate)
mod_delayed_work(cifsiod_wq, &server->echo, 0);
wake_up(&server->response_q);
return rc;
}
int cifs_reconnect(struct TCP_Server_Info *server)
{
int rc;
struct super_block *sb;
struct cifs_sb_info *cifs_sb;
/*
* If tcp session is not an dfs connection or it is a channel, then reconnect to last target
* server.
*/
spin_lock(&cifs_tcp_ses_lock);
if (!server->is_dfs_conn || server->is_channel) {
spin_unlock(&cifs_tcp_ses_lock);
return __cifs_reconnect(server);
}
spin_unlock(&cifs_tcp_ses_lock);
/* If no superblock, then it might be an ipc connection */
sb = cifs_get_tcp_super(server);
if (IS_ERR(sb))
return __cifs_reconnect(server);
/*
* Check for a referral path to look up in superblock. If unset, then simply reconnect to
* last target server.
*/
cifs_sb = CIFS_SB(sb);
if (!cifs_sb->origin_fullpath || !cifs_sb->origin_fullpath[0])
rc = __cifs_reconnect(server);
else
rc = reconnect_dfs_server(server, cifs_sb);
cifs_put_tcp_super(sb);
return rc;
}
#else
int cifs_reconnect(struct TCP_Server_Info *server)
{
return __cifs_reconnect(server);
}
#endif
static void
cifs_echo_request(struct work_struct *work)
{