Skip to content

Commit

Permalink
cifs: split out dfs code from cifs_reconnect()
Browse files Browse the repository at this point in the history
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) <[email protected]>
Reviewed-by: Shyam Prasad N <[email protected]>
Signed-off-by: Steve French <[email protected]>
  • Loading branch information
pcacjr authored and Steve French committed Nov 10, 2021
1 parent ae0abb4 commit bbcce36
Showing 1 changed file with 162 additions and 133 deletions.
295 changes: 162 additions & 133 deletions fs/cifs/connect.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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
*
Expand All @@ -286,99 +250,32 @@ 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))
rc = smbd_reconnect(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);
Expand All @@ -392,26 +289,158 @@ cifs_reconnect(struct TCP_Server_Info *server)
}
} while (server->tcpStatus == CifsNeedReconnect);

if (server->tcpStatus == CifsNeedNegotiate)
mod_delayed_work(cifsiod_wq, &server->echo, 0);

wake_up(&server->response_q);
return rc;
}

#ifdef CONFIG_CIFS_DFS_UPCALL
if (tgt_it) {
rc = dfs_cache_noreq_update_tgthint(cifs_sb->origin_fullpath + 1,
tgt_it);
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) {
cifs_server_dbg(VFS, "%s: failed to update DFS target hint: rc = %d\n",
__func__, 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;
}
dfs_cache_free_tgts(&tgt_list);
}

cifs_put_tcp_super(sb);
#endif
/*
* 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)
{
Expand Down

0 comments on commit bbcce36

Please sign in to comment.