Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
2c8b7bb
BACKPORT: cifs: enable multichannel on the non-DFS mount path
aaptel Sep 12, 2026
2c9d770
BACKPORT: cifs: keep secondary-channel hostnames empty
sprasad-microsoft Sep 12, 2026
082e360
BACKPORT: cifs: use the least loaded channel for requests
sprasad-microsoft Sep 12, 2026
19a567f
BACKPORT: cifs: reduce channel loading lag during selection
sprasad-microsoft Sep 12, 2026
03686fb
BACKPORT: cifs: select only eligible multichannel transports
sprasad-microsoft Sep 12, 2026
a946265
BACKPORT: cifs: add per-channel reconnect state
sprasad-microsoft Sep 12, 2026
7f7e12f
BACKPORT: cifs: reconnect individual multichannel transports
sprasad-microsoft Sep 12, 2026
a02076c
BACKPORT: cifs: harden multichannel reconnect concurrency
sprasad-microsoft Sep 12, 2026
6e26c0a
BACKPORT: cifs: integrate reconnect-aware channel selection
sprasad-microsoft Sep 12, 2026
8373b4b
BACKPORT: cifs: use session reconnect state in full-key ioctl
sprasad-microsoft Sep 12, 2026
eb3a610
BACKPORT: cifs: repick a channel when retrying direct reads
aaptel Sep 12, 2026
f473352
BACKPORT: cifs: track replay state for write retries
sprasad-microsoft Sep 12, 2026
49cc984
BACKPORT: cifs: repick and replay retried direct writes
sprasad-microsoft Sep 12, 2026
38f398c
BACKPORT: cifs: normalize replayable transport send errors
sprasad-microsoft Sep 12, 2026
9cee56f
BACKPORT: cifs: make SMB2 flush replay safe across channels
sprasad-microsoft Sep 12, 2026
3294f2c
BACKPORT: cifs: close multichannel write retry paths
sprasad-microsoft Sep 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions fs/cifs/cifs_debug.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ cifs_dump_channel(struct seq_file *m, int i, struct cifs_chan *chan)
in_flight(server),
atomic_read(&server->in_send),
atomic_read(&server->num_waiters));
if (chan->in_reconnect)
seq_puts(m, "\n\t\t[RECONNECTING]");
}

static void
Expand Down Expand Up @@ -382,15 +384,15 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
list_for_each(tmp2, &server->smb_ses_list) {
ses = list_entry(tmp2, struct cifs_ses,
smb_ses_list);
if (ses->status == CifsExiting)
if (cifs_ses_exiting(ses))
continue;
i++;
if ((ses->serverDomain == NULL) ||
(ses->serverOS == NULL) ||
(ses->serverNOS == NULL)) {
seq_printf(m, "\n\t%d) Address: %s Uses: %d Capability: 0x%x\tSession Status: %d ",
i, ses->ip_addr, ses->ses_count,
ses->capabilities, ses->status);
ses->capabilities, cifs_get_ses_status(ses));
if (ses->session_flags & SMB2_SESSION_FLAG_IS_GUEST)
seq_printf(m, "Guest ");
else if (ses->session_flags & SMB2_SESSION_FLAG_IS_NULL)
Expand All @@ -402,7 +404,7 @@ static int cifs_debug_data_proc_show(struct seq_file *m, void *v)
"\n\tSMB session status: %d ",
i, ses->ip_addr, ses->serverDomain,
ses->ses_count, ses->serverOS, ses->serverNOS,
ses->capabilities, ses->status);
ses->capabilities, cifs_get_ses_status(ses));
}

seq_printf(m, "\n\tSecurity type: %s ",
Expand Down
79 changes: 71 additions & 8 deletions fs/cifs/cifsglob.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,26 @@
* CIFS vfs client Status information (based on what we know.)
*/

/* associated with each tcp and smb session */
/* associated with each transport connection */
enum statusEnum {
CifsNew = 0,
CifsGood,
CifsExiting,
CifsNeedReconnect,
CifsNeedNegotiate
CifsNeedNegotiate,
CifsInNegotiate,
CifsNeedTcon,
CifsInTcon,
CifsInFilesInvalidate
};

/* associated with each SMB session */
enum ses_status_enum {
SES_NEW = 0,
SES_GOOD,
SES_EXITING,
SES_NEED_RECON,
SES_IN_SETUP
};

enum securityEnum {
Expand Down Expand Up @@ -690,6 +703,17 @@ struct TCP_Server_Info {
int nr_targets;
bool noblockcnt; /* use non-blocking connect() */
bool is_channel; /* if a session channel */
/*
* Primary transport owning the SMB session list. Primary connections
* point to themselves; secondary multichannel transports point to the
* session's original transport.
*/
struct TCP_Server_Info *primary_server;
/*
* SMB3 ChannelSequence is primary/session scoped. Increment it whenever
* any channel reconnects and use the primary value on all channels.
*/
__u16 channel_sequence_num;
#ifdef CONFIG_CIFS_SWN_UPCALL
bool use_swn_dstaddr;
struct sockaddr_storage swn_dstaddr;
Expand Down Expand Up @@ -897,6 +921,7 @@ struct cifs_server_iface {
};

struct cifs_chan {
unsigned int in_reconnect : 1; /* Session Setup in progress on this channel */
struct TCP_Server_Info *server;
__u8 signkey[SMB3_SIGN_KEY_SIZE];
};
Expand All @@ -906,12 +931,14 @@ struct cifs_chan {
*/
struct cifs_ses {
struct list_head smb_ses_list;
struct list_head rlist; /* reconnect list */
struct list_head tcon_list;
struct cifs_tcon *tcon_ipc;
struct mutex session_mutex;
spinlock_t ses_lock; /* protects ses_status */
struct TCP_Server_Info *server; /* pointer to server info */
int ses_count; /* reference counter */
enum statusEnum status; /* updates protected by GlobalMid_Lock */
enum ses_status_enum ses_status;
unsigned overrideSecFlg; /* if non-zero override global sec flags */
char *serverOS; /* name of operating system underlying server */
char *serverNOS; /* name of network operating system of server */
Expand Down Expand Up @@ -959,11 +986,33 @@ struct cifs_ses {
spinlock_t chan_lock;
/* ========= begin: protected by chan_lock ======== */
#define CIFS_MAX_CHANNELS 16
#define CIFS_ALL_CHANNELS_SET(ses) \
((1UL << (ses)->chan_count) - 1)
#define CIFS_ALL_CHANS_GOOD(ses) \
(!(ses)->chans_need_reconnect)
#define CIFS_ALL_CHANS_NEED_RECONNECT(ses) \
((ses)->chans_need_reconnect == CIFS_ALL_CHANNELS_SET(ses))
#define CIFS_CHAN_NEEDS_RECONNECT(ses, index) \
test_bit((index), &(ses)->chans_need_reconnect)
#define CIFS_SET_ALL_CHANS_NEED_RECONNECT(ses) \
((ses)->chans_need_reconnect = CIFS_ALL_CHANNELS_SET(ses))
#define CIFS_CHAN_IN_RECONNECT(ses, index) \
((ses)->chans[(index)].in_reconnect)

struct cifs_chan chans[CIFS_MAX_CHANNELS];
struct cifs_chan *binding_chan;
size_t chan_count;
size_t chan_max;
atomic_t chan_seq; /* round robin state */

/*
* Bitmap of channels that require connection/session recovery.
*
* Track reconnect state per channel while another established transport
* can keep the SMB session usable. The bitmap is protected by chan_lock.
* Keep channel eligibility separate from the session-wide state.
*/
unsigned long chans_need_reconnect;
/* ========= end: protected by chan_lock ======== */
};

Expand Down Expand Up @@ -1357,6 +1406,7 @@ struct cifs_writedata {
pid_t pid;
unsigned int bytes;
int result;
bool replay; /* retry may have reached server */
struct TCP_Server_Info *server;
#ifdef CONFIG_CIFS_SMB_DIRECT
struct smbd_mr *mr;
Expand Down Expand Up @@ -1707,6 +1757,12 @@ static inline bool is_retryable_error(int error)
}


static inline bool is_replayable_error(int error)
{
return error == -EAGAIN || error == -ECONNABORTED;
}


/* cifs_get_writable_file() flags */
#define FIND_WR_ANY 0
#define FIND_WR_FSUID_ONLY 1
Expand Down Expand Up @@ -1799,7 +1855,7 @@ require use of the stronger protocol */
* list operations on pending_mid_q and oplockQ
* updates to XID counters, multiplex id and SMB sequence numbers
* list operations on global DnotifyReqList
* updates to ses->status and TCP_Server_Info->tcpStatus
* TCP_Server_Info->tcpStatus (session state uses ses->ses_lock)
* updates to server->CurrentMid
* tcp_ses_lock protects:
* list operations on tcp and SMB session lists
Expand Down Expand Up @@ -2042,12 +2098,19 @@ static inline struct scatterlist *cifs_sg_set_buf(struct scatterlist *sg,
return sg;
}

static inline bool cifs_ses_exiting(struct cifs_ses *ses)
static inline enum ses_status_enum cifs_get_ses_status(struct cifs_ses *ses)
{
bool ret;
enum ses_status_enum status;

spin_lock(&ses->ses_lock);
status = ses->ses_status;
spin_unlock(&ses->ses_lock);
return status;
}

ret = ses->status == CifsExiting;
return ret;
static inline bool cifs_ses_exiting(struct cifs_ses *ses)
{
return cifs_get_ses_status(ses) == SES_EXITING;
}

#endif /* _CIFS_GLOB_H */
26 changes: 26 additions & 0 deletions fs/cifs/cifsproto.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ extern int SendReceiveBlockingLock(const unsigned int xid,
struct smb_hdr *out_buf,
int *bytes_returned);
extern int cifs_reconnect(struct TCP_Server_Info *server);
extern void cifs_signal_cifsd_for_reconnect(struct TCP_Server_Info *server,
bool all_channels);
extern int checkSMB(char *buf, unsigned int len, struct TCP_Server_Info *srvr);
extern bool is_valid_oplock_break(char *, struct TCP_Server_Info *);
extern bool backup_cred(struct cifs_sb_info *);
Expand Down Expand Up @@ -594,6 +596,30 @@ void rqst_page_get_length(const struct smb_rqst *rqst, unsigned int page,
unsigned int *len, unsigned int *offset);
struct cifs_chan *
cifs_ses_find_chan(struct cifs_ses *ses, struct TCP_Server_Info *server);
unsigned int
cifs_ses_get_chan_index(struct cifs_ses *ses,
struct TCP_Server_Info *server);
struct cifs_chan *
cifs_ses_find_chan_locked(struct cifs_ses *ses,
struct TCP_Server_Info *server);
void
cifs_chan_set_in_reconnect(struct cifs_ses *ses,
struct TCP_Server_Info *server);
void
cifs_chan_clear_in_reconnect(struct cifs_ses *ses,
struct TCP_Server_Info *server);
bool
cifs_chan_in_reconnect(struct cifs_ses *ses,
struct TCP_Server_Info *server);
void
cifs_chan_set_need_reconnect(struct cifs_ses *ses,
struct TCP_Server_Info *server);
void
cifs_chan_clear_need_reconnect(struct cifs_ses *ses,
struct TCP_Server_Info *server);
bool
cifs_chan_needs_reconnect(struct cifs_ses *ses,
struct TCP_Server_Info *server);
int cifs_try_adding_channels(struct cifs_sb_info *cifs_sb, struct cifs_ses *ses);
bool is_server_using_iface(struct TCP_Server_Info *server,
struct cifs_server_iface *iface);
Expand Down
30 changes: 22 additions & 8 deletions fs/cifs/cifssmb.c
Original file line number Diff line number Diff line change
Expand Up @@ -1885,13 +1885,23 @@ cifs_writev_requeue(struct cifs_writedata *wdata)
struct TCP_Server_Info *server;
unsigned int rest_len;

server = tlink_tcon(wdata->cfile->tlink)->ses->server;
i = 0;
rest_len = wdata->bytes;
do {
struct cifs_writedata *wdata2;
struct cifs_tcon *tcon = tlink_tcon(wdata->cfile->tlink);
unsigned int j, nr_pages, wsize, tailsz, cur_len;

/*
* Retry writeback on a healthy channel. Bind it before sizing
* and sending so one transport owns the complete retry attempt.
*/
server = cifs_pick_channel(tcon->ses);
if (!server) {
rc = -EIO;
break;
}

wsize = server->ops->wp_retry_size(inode);
if (wsize < rest_len) {
nr_pages = wsize / PAGE_SIZE;
Expand Down Expand Up @@ -1920,6 +1930,8 @@ cifs_writev_requeue(struct cifs_writedata *wdata)
}

wdata2->sync_mode = wdata->sync_mode;
wdata2->server = server;
wdata2->replay = true;
wdata2->nr_pages = nr_pages;
wdata2->offset = page_offset(wdata2->pages[0]);
wdata2->pagesz = PAGE_SIZE;
Expand All @@ -1931,7 +1943,7 @@ cifs_writev_requeue(struct cifs_writedata *wdata)
if (!wdata2->cfile) {
cifs_dbg(VFS, "No writable handle to retry writepages rc=%d\n",
rc);
if (!is_retryable_error(rc))
if (!is_retryable_error(rc) && !is_replayable_error(rc))
rc = -EBADF;
} else {
wdata2->pid = wdata2->cfile->pid;
Expand All @@ -1941,7 +1953,8 @@ cifs_writev_requeue(struct cifs_writedata *wdata)

for (j = 0; j < nr_pages; j++) {
unlock_page(wdata2->pages[j]);
if (rc != 0 && !is_retryable_error(rc)) {
if (rc != 0 && !is_retryable_error(rc) &&
!is_replayable_error(rc)) {
SetPageError(wdata2->pages[j]);
end_page_writeback(wdata2->pages[j]);
put_page(wdata2->pages[j]);
Expand All @@ -1950,7 +1963,7 @@ cifs_writev_requeue(struct cifs_writedata *wdata)

kref_put(&wdata2->refcount, cifs_writedata_release);
if (rc) {
if (is_retryable_error(rc))
if (is_retryable_error(rc) || is_replayable_error(rc))
continue;
i += nr_pages;
break;
Expand All @@ -1967,7 +1980,7 @@ cifs_writev_requeue(struct cifs_writedata *wdata)
put_page(wdata->pages[i]);
}

if (rc != 0 && !is_retryable_error(rc))
if (rc != 0 && !is_retryable_error(rc) && !is_replayable_error(rc))
mapping_set_error(inode->i_mapping, rc);
kref_put(&wdata->refcount, cifs_writedata_release);
}
Expand All @@ -1986,20 +1999,21 @@ cifs_writev_complete(struct work_struct *work)
spin_unlock(&inode->i_lock);
cifs_stats_bytes_written(tlink_tcon(wdata->cfile->tlink),
wdata->bytes);
} else if (wdata->sync_mode == WB_SYNC_ALL && wdata->result == -EAGAIN)
} else if (wdata->sync_mode == WB_SYNC_ALL &&
is_replayable_error(wdata->result))
return cifs_writev_requeue(wdata);

for (i = 0; i < wdata->nr_pages; i++) {
struct page *page = wdata->pages[i];
if (wdata->result == -EAGAIN)
if (is_replayable_error(wdata->result))
__set_page_dirty_nobuffers(page);
else if (wdata->result < 0)
SetPageError(page);
end_page_writeback(page);
cifs_readpage_to_fscache(inode, page);
put_page(page);
}
if (wdata->result != -EAGAIN)
if (!is_replayable_error(wdata->result))
mapping_set_error(inode->i_mapping, wdata->result);
kref_put(&wdata->refcount, cifs_writedata_release);
}
Expand Down
Loading