Commit 724244cd authored by Shyam Prasad N's avatar Shyam Prasad N Committed by Steve French

cifs: protect session channel fields with chan_lock

Introducing a new spin lock to protect all the channel related
fields in a cifs_ses struct. This lock should be taken
whenever dealing with the channel fields, and should be held
only for very short intervals which will not sleep.

Currently, all channel related fields in cifs_ses structure
are protected by session_mutex. However, this mutex is held for
long periods (sometimes while waiting for a reply from server).
This makes the codepath quite tricky to change.
Signed-off-by: default avatarShyam Prasad N <sprasad@microsoft.com>
Reviewed-by: default avatarPaulo Alcantara (SUSE) <pc@cjr.nz>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 8e07757b
...@@ -414,12 +414,14 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v) ...@@ -414,12 +414,14 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
from_kuid(&init_user_ns, ses->linux_uid), from_kuid(&init_user_ns, ses->linux_uid),
from_kuid(&init_user_ns, ses->cred_uid)); from_kuid(&init_user_ns, ses->cred_uid));
spin_lock(&ses->chan_lock);
if (ses->chan_count > 1) { if (ses->chan_count > 1) {
seq_printf(m, "\n\n\tExtra Channels: %zu ", seq_printf(m, "\n\n\tExtra Channels: %zu ",
ses->chan_count-1); ses->chan_count-1);
for (j = 1; j < ses->chan_count; j++) for (j = 1; j < ses->chan_count; j++)
cifs_dump_channel(m, j, &ses->chans[j]); cifs_dump_channel(m, j, &ses->chans[j]);
} }
spin_unlock(&ses->chan_lock);
seq_puts(m, "\n\n\tShares: "); seq_puts(m, "\n\n\tShares: ");
j = 0; j = 0;
......
...@@ -952,16 +952,21 @@ struct cifs_ses { ...@@ -952,16 +952,21 @@ struct cifs_ses {
* iface_lock should be taken when accessing any of these fields * iface_lock should be taken when accessing any of these fields
*/ */
spinlock_t iface_lock; spinlock_t iface_lock;
/* ========= begin: protected by iface_lock ======== */
struct cifs_server_iface *iface_list; struct cifs_server_iface *iface_list;
size_t iface_count; size_t iface_count;
unsigned long iface_last_update; /* jiffies */ unsigned long iface_last_update; /* jiffies */
/* ========= end: protected by iface_lock ======== */
spinlock_t chan_lock;
/* ========= begin: protected by chan_lock ======== */
#define CIFS_MAX_CHANNELS 16 #define CIFS_MAX_CHANNELS 16
struct cifs_chan chans[CIFS_MAX_CHANNELS]; struct cifs_chan chans[CIFS_MAX_CHANNELS];
struct cifs_chan *binding_chan; struct cifs_chan *binding_chan;
size_t chan_count; size_t chan_count;
size_t chan_max; size_t chan_max;
atomic_t chan_seq; /* round robin state */ atomic_t chan_seq; /* round robin state */
/* ========= end: protected by chan_lock ======== */
}; };
/* /*
......
...@@ -1577,8 +1577,12 @@ static int match_session(struct cifs_ses *ses, struct smb3_fs_context *ctx) ...@@ -1577,8 +1577,12 @@ static int match_session(struct cifs_ses *ses, struct smb3_fs_context *ctx)
* If an existing session is limited to less channels than * If an existing session is limited to less channels than
* requested, it should not be reused * requested, it should not be reused
*/ */
if (ses->chan_max < ctx->max_channels) spin_lock(&ses->chan_lock);
if (ses->chan_max < ctx->max_channels) {
spin_unlock(&ses->chan_lock);
return 0; return 0;
}
spin_unlock(&ses->chan_lock);
switch (ses->sectype) { switch (ses->sectype) {
case Kerberos: case Kerberos:
...@@ -1713,6 +1717,7 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) ...@@ -1713,6 +1717,7 @@ cifs_find_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
void cifs_put_smb_ses(struct cifs_ses *ses) void cifs_put_smb_ses(struct cifs_ses *ses)
{ {
unsigned int rc, xid; unsigned int rc, xid;
unsigned int chan_count;
struct TCP_Server_Info *server = ses->server; struct TCP_Server_Info *server = ses->server;
cifs_dbg(FYI, "%s: ses_count=%d\n", __func__, ses->ses_count); cifs_dbg(FYI, "%s: ses_count=%d\n", __func__, ses->ses_count);
...@@ -1754,12 +1759,24 @@ void cifs_put_smb_ses(struct cifs_ses *ses) ...@@ -1754,12 +1759,24 @@ void cifs_put_smb_ses(struct cifs_ses *ses)
list_del_init(&ses->smb_ses_list); list_del_init(&ses->smb_ses_list);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
spin_lock(&ses->chan_lock);
chan_count = ses->chan_count;
spin_unlock(&ses->chan_lock);
/* close any extra channels */ /* close any extra channels */
if (ses->chan_count > 1) { if (chan_count > 1) {
int i; int i;
for (i = 1; i < ses->chan_count; i++) for (i = 1; i < chan_count; i++) {
/*
* note: for now, we're okay accessing ses->chans
* without chan_lock. But when chans can go away, we'll
* need to introduce ref counting to make sure that chan
* is not freed from under us.
*/
cifs_put_tcp_session(ses->chans[i].server, 0); cifs_put_tcp_session(ses->chans[i].server, 0);
ses->chans[i].server = NULL;
}
} }
sesInfoFree(ses); sesInfoFree(ses);
...@@ -2018,9 +2035,11 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) ...@@ -2018,9 +2035,11 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx)
mutex_lock(&ses->session_mutex); mutex_lock(&ses->session_mutex);
/* add server as first channel */ /* add server as first channel */
spin_lock(&ses->chan_lock);
ses->chans[0].server = server; ses->chans[0].server = server;
ses->chan_count = 1; ses->chan_count = 1;
ses->chan_max = ctx->multichannel ? ctx->max_channels:1; ses->chan_max = ctx->multichannel ? ctx->max_channels:1;
spin_unlock(&ses->chan_lock);
rc = cifs_negotiate_protocol(xid, ses); rc = cifs_negotiate_protocol(xid, ses);
if (!rc) if (!rc)
......
...@@ -75,6 +75,7 @@ sesInfoAlloc(void) ...@@ -75,6 +75,7 @@ sesInfoAlloc(void)
INIT_LIST_HEAD(&ret_buf->tcon_list); INIT_LIST_HEAD(&ret_buf->tcon_list);
mutex_init(&ret_buf->session_mutex); mutex_init(&ret_buf->session_mutex);
spin_lock_init(&ret_buf->iface_lock); spin_lock_init(&ret_buf->iface_lock);
spin_lock_init(&ret_buf->chan_lock);
} }
return ret_buf; return ret_buf;
} }
......
...@@ -54,41 +54,53 @@ bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface) ...@@ -54,41 +54,53 @@ bool is_ses_using_iface(struct cifs_ses *ses, struct cifs_server_iface *iface)
{ {
int i; int i;
spin_lock(&ses->chan_lock);
for (i = 0; i < ses->chan_count; i++) { for (i = 0; i < ses->chan_count; i++) {
if (is_server_using_iface(ses->chans[i].server, iface)) if (is_server_using_iface(ses->chans[i].server, iface)) {
spin_unlock(&ses->chan_lock);
return true; return true;
}
} }
spin_unlock(&ses->chan_lock);
return false; return false;
} }
/* returns number of channels added */ /* returns number of channels added */
int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses) int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
{ {
int old_chan_count = ses->chan_count; int old_chan_count, new_chan_count;
int left = ses->chan_max - ses->chan_count; int left;
int i = 0; int i = 0;
int rc = 0; int rc = 0;
int tries = 0; int tries = 0;
struct cifs_server_iface *ifaces = NULL; struct cifs_server_iface *ifaces = NULL;
size_t iface_count; size_t iface_count;
if (ses->server->dialect < SMB30_PROT_ID) {
cifs_dbg(VFS, "multichannel is not supported on this protocol version, use 3.0 or above\n");
return 0;
}
spin_lock(&ses->chan_lock);
new_chan_count = old_chan_count = ses->chan_count;
left = ses->chan_max - ses->chan_count;
if (left <= 0) { if (left <= 0) {
cifs_dbg(FYI, cifs_dbg(FYI,
"ses already at max_channels (%zu), nothing to open\n", "ses already at max_channels (%zu), nothing to open\n",
ses->chan_max); ses->chan_max);
return 0; spin_unlock(&ses->chan_lock);
}
if (ses->server->dialect < SMB30_PROT_ID) {
cifs_dbg(VFS, "multichannel is not supported on this protocol version, use 3.0 or above\n");
return 0; return 0;
} }
if (!(ses->server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) { if (!(ses->server->capabilities & SMB2_GLOBAL_CAP_MULTI_CHANNEL)) {
cifs_dbg(VFS, "server %s does not support multichannel\n", ses->server->hostname); cifs_dbg(VFS, "server %s does not support multichannel\n", ses->server->hostname);
ses->chan_max = 1; ses->chan_max = 1;
spin_unlock(&ses->chan_lock);
return 0; return 0;
} }
spin_unlock(&ses->chan_lock);
/* /*
* Make a copy of the iface list at the time and use that * Make a copy of the iface list at the time and use that
...@@ -142,10 +154,11 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses) ...@@ -142,10 +154,11 @@ int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses)
cifs_dbg(FYI, "successfully opened new channel on iface#%d\n", cifs_dbg(FYI, "successfully opened new channel on iface#%d\n",
i); i);
left--; left--;
new_chan_count++;
} }
kfree(ifaces); kfree(ifaces);
return ses->chan_count - old_chan_count; return new_chan_count - old_chan_count;
} }
/* /*
...@@ -157,10 +170,14 @@ cifs_ses_find_chan(struct cifs_ses *ses, struct TCP_Server_Info *server) ...@@ -157,10 +170,14 @@ cifs_ses_find_chan(struct cifs_ses *ses, struct TCP_Server_Info *server)
{ {
int i; int i;
spin_lock(&ses->chan_lock);
for (i = 0; i < ses->chan_count; i++) { for (i = 0; i < ses->chan_count; i++) {
if (ses->chans[i].server == server) if (ses->chans[i].server == server) {
spin_unlock(&ses->chan_lock);
return &ses->chans[i]; return &ses->chans[i];
}
} }
spin_unlock(&ses->chan_lock);
return NULL; return NULL;
} }
...@@ -168,6 +185,7 @@ static int ...@@ -168,6 +185,7 @@ static int
cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
struct cifs_server_iface *iface) struct cifs_server_iface *iface)
{ {
struct TCP_Server_Info *chan_server;
struct cifs_chan *chan; struct cifs_chan *chan;
struct smb3_fs_context ctx = {NULL}; struct smb3_fs_context ctx = {NULL};
static const char unc_fmt[] = "\\%s\\foo"; static const char unc_fmt[] = "\\%s\\foo";
...@@ -240,15 +258,20 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, ...@@ -240,15 +258,20 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
SMB2_CLIENT_GUID_SIZE); SMB2_CLIENT_GUID_SIZE);
ctx.use_client_guid = true; ctx.use_client_guid = true;
mutex_lock(&ses->session_mutex); chan_server = cifs_get_tcp_session(&ctx);
mutex_lock(&ses->session_mutex);
spin_lock(&ses->chan_lock);
chan = ses->binding_chan = &ses->chans[ses->chan_count]; chan = ses->binding_chan = &ses->chans[ses->chan_count];
chan->server = cifs_get_tcp_session(&ctx); chan->server = chan_server;
if (IS_ERR(chan->server)) { if (IS_ERR(chan->server)) {
rc = PTR_ERR(chan->server); rc = PTR_ERR(chan->server);
chan->server = NULL; chan->server = NULL;
spin_unlock(&ses->chan_lock);
goto out; goto out;
} }
spin_unlock(&ses->chan_lock);
spin_lock(&cifs_tcp_ses_lock); spin_lock(&cifs_tcp_ses_lock);
chan->server->is_channel = true; chan->server->is_channel = true;
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
...@@ -283,8 +306,11 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses, ...@@ -283,8 +306,11 @@ cifs_ses_add_channel(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses,
* ses to the new server. * ses to the new server.
*/ */
spin_lock(&ses->chan_lock);
ses->chan_count++; ses->chan_count++;
atomic_set(&ses->chan_seq, 0); atomic_set(&ses->chan_seq, 0);
spin_unlock(&ses->chan_lock);
out: out:
ses->binding = false; ses->binding = false;
ses->binding_chan = NULL; ses->binding_chan = NULL;
......
...@@ -1044,14 +1044,17 @@ struct TCP_Server_Info *cifs_pick_channel(struct cifs_ses *ses) ...@@ -1044,14 +1044,17 @@ struct TCP_Server_Info *cifs_pick_channel(struct cifs_ses *ses)
if (!ses) if (!ses)
return NULL; return NULL;
spin_lock(&ses->chan_lock);
if (!ses->binding) { if (!ses->binding) {
/* round robin */ /* round robin */
if (ses->chan_count > 1) { if (ses->chan_count > 1) {
index = (uint)atomic_inc_return(&ses->chan_seq); index = (uint)atomic_inc_return(&ses->chan_seq);
index %= ses->chan_count; index %= ses->chan_count;
} }
spin_unlock(&ses->chan_lock);
return ses->chans[index].server; return ses->chans[index].server;
} else { } else {
spin_unlock(&ses->chan_lock);
return cifs_ses_server(ses); return cifs_ses_server(ses);
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment