Skip to content

Commit

Permalink
cifs: Fix lack of credit renegotiation on read retry
Browse files Browse the repository at this point in the history
When netfslib asks cifs to issue a read operation, it prefaces this with a
call to ->clamp_length() which cifs uses to negotiate credits, providing
receive capacity on the server; however, in the event that a read op needs
reissuing, netfslib doesn't call ->clamp_length() again as that could
shorten the subrequest, leaving a gap.

This causes the retried read to be done with zero credits which causes the
server to reject it with STATUS_INVALID_PARAMETER.  This is a problem for a
DIO read that is requested that would go over the EOF.  The short read will
be retried, causing EINVAL to be returned to the user when it fails.

Fix this by making cifs_req_issue_read() negotiate new credits if retrying
(NETFS_SREQ_RETRYING now gets set in the read side as well as the write
side in this instance).

This isn't sufficient, however: the new credits might not be sufficient to
complete the remainder of the read, so also add an additional field,
rreq->actual_len, that holds the actual size of the op we want to perform
without having to alter subreq->len.

We then rely on repeated short reads being retried until we finish the read
or reach the end of file and make a zero-length read.

Also fix a couple of places where the subrequest start and length need to
be altered by the amount so far transferred when being used.

Fixes: 69c3c02 ("cifs: Implement netfslib hooks")
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Steve French <sfrench@samba.org>
cc: Paulo Alcantara <pc@manguebit.com>
cc: Jeff Layton <jlayton@kernel.org>
cc: linux-cifs@vger.kernel.org
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Steve French <stfrench@microsoft.com>
  • Loading branch information
David Howells authored and Steve French committed Aug 28, 2024
1 parent 86987d8 commit 6a5dcd4
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 16 deletions.
2 changes: 2 additions & 0 deletions fs/netfs/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,15 @@ static bool netfs_rreq_perform_resubmissions(struct netfs_io_request *rreq)
break;
subreq->source = NETFS_DOWNLOAD_FROM_SERVER;
subreq->error = 0;
__set_bit(NETFS_SREQ_RETRYING, &subreq->flags);
netfs_stat(&netfs_n_rh_download_instead);
trace_netfs_sreq(subreq, netfs_sreq_trace_download_instead);
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
atomic_inc(&rreq->nr_outstanding);
netfs_reset_subreq_iter(rreq, subreq);
netfs_read_from_server(rreq, subreq);
} else if (test_bit(NETFS_SREQ_SHORT_IO, &subreq->flags)) {
__set_bit(NETFS_SREQ_RETRYING, &subreq->flags);
netfs_reset_subreq_iter(rreq, subreq);
netfs_rreq_short_read(rreq, subreq);
}
Expand Down
1 change: 1 addition & 0 deletions fs/smb/client/cifsglob.h
Original file line number Diff line number Diff line change
Expand Up @@ -1485,6 +1485,7 @@ struct cifs_io_subrequest {
struct cifs_io_request *req;
};
ssize_t got_bytes;
size_t actual_len;
unsigned int xid;
int result;
bool have_xid;
Expand Down
37 changes: 33 additions & 4 deletions fs/smb/client/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ static void cifs_issue_write(struct netfs_io_subrequest *subreq)
goto fail;
}

wdata->actual_len = wdata->subreq.len;
rc = adjust_credits(wdata->server, wdata, cifs_trace_rw_credits_issue_write_adjust);
if (rc)
goto fail;
Expand Down Expand Up @@ -153,7 +154,7 @@ static bool cifs_clamp_length(struct netfs_io_subrequest *subreq)
struct cifs_io_request *req = container_of(subreq->rreq, struct cifs_io_request, rreq);
struct TCP_Server_Info *server = req->server;
struct cifs_sb_info *cifs_sb = CIFS_SB(rreq->inode->i_sb);
size_t rsize = 0;
size_t rsize;
int rc;

rdata->xid = get_xid();
Expand All @@ -166,8 +167,8 @@ static bool cifs_clamp_length(struct netfs_io_subrequest *subreq)
cifs_sb->ctx);


rc = server->ops->wait_mtu_credits(server, cifs_sb->ctx->rsize, &rsize,
&rdata->credits);
rc = server->ops->wait_mtu_credits(server, cifs_sb->ctx->rsize,
&rsize, &rdata->credits);
if (rc) {
subreq->error = rc;
return false;
Expand All @@ -183,7 +184,8 @@ static bool cifs_clamp_length(struct netfs_io_subrequest *subreq)
server->credits, server->in_flight, 0,
cifs_trace_rw_credits_read_submit);

subreq->len = min_t(size_t, subreq->len, rsize);
subreq->len = umin(subreq->len, rsize);
rdata->actual_len = subreq->len;

#ifdef CONFIG_CIFS_SMB_DIRECT
if (server->smbd_conn)
Expand All @@ -203,12 +205,39 @@ static void cifs_req_issue_read(struct netfs_io_subrequest *subreq)
struct netfs_io_request *rreq = subreq->rreq;
struct cifs_io_subrequest *rdata = container_of(subreq, struct cifs_io_subrequest, subreq);
struct cifs_io_request *req = container_of(subreq->rreq, struct cifs_io_request, rreq);
struct TCP_Server_Info *server = req->server;
struct cifs_sb_info *cifs_sb = CIFS_SB(rreq->inode->i_sb);
int rc = 0;

cifs_dbg(FYI, "%s: op=%08x[%x] mapping=%p len=%zu/%zu\n",
__func__, rreq->debug_id, subreq->debug_index, rreq->mapping,
subreq->transferred, subreq->len);

if (test_bit(NETFS_SREQ_RETRYING, &subreq->flags)) {
/*
* As we're issuing a retry, we need to negotiate some new
* credits otherwise the server may reject the op with
* INVALID_PARAMETER. Note, however, we may get back less
* credit than we need to complete the op, in which case, we
* shorten the op and rely on additional rounds of retry.
*/
size_t rsize = umin(subreq->len - subreq->transferred,
cifs_sb->ctx->rsize);

rc = server->ops->wait_mtu_credits(server, rsize, &rdata->actual_len,
&rdata->credits);
if (rc)
goto out;

rdata->credits.in_flight_check = 1;

trace_smb3_rw_credits(rdata->rreq->debug_id,
rdata->subreq.debug_index,
rdata->credits.value,
server->credits, server->in_flight, 0,
cifs_trace_rw_credits_read_resubmit);
}

if (req->cfile->invalidHandle) {
do {
rc = cifs_reopen_file(req->cfile, true);
Expand Down
2 changes: 1 addition & 1 deletion fs/smb/client/smb2ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ smb2_adjust_credits(struct TCP_Server_Info *server,
unsigned int /*enum smb3_rw_credits_trace*/ trace)
{
struct cifs_credits *credits = &subreq->credits;
int new_val = DIV_ROUND_UP(subreq->subreq.len, SMB2_MAX_BUFFER_SIZE);
int new_val = DIV_ROUND_UP(subreq->actual_len, SMB2_MAX_BUFFER_SIZE);
int scredits, in_flight;

if (!credits->value || credits->value == new_val)
Expand Down
28 changes: 17 additions & 11 deletions fs/smb/client/smb2pdu.c
Original file line number Diff line number Diff line change
Expand Up @@ -4529,9 +4529,9 @@ smb2_readv_callback(struct mid_q_entry *mid)
"rdata server %p != mid server %p",
rdata->server, mid->server);

cifs_dbg(FYI, "%s: mid=%llu state=%d result=%d bytes=%zu\n",
cifs_dbg(FYI, "%s: mid=%llu state=%d result=%d bytes=%zu/%zu\n",
__func__, mid->mid, mid->mid_state, rdata->result,
rdata->subreq.len);
rdata->actual_len, rdata->subreq.len - rdata->subreq.transferred);

switch (mid->mid_state) {
case MID_RESPONSE_RECEIVED:
Expand Down Expand Up @@ -4585,15 +4585,18 @@ smb2_readv_callback(struct mid_q_entry *mid)
rdata->subreq.debug_index,
rdata->xid,
rdata->req->cfile->fid.persistent_fid,
tcon->tid, tcon->ses->Suid, rdata->subreq.start,
rdata->subreq.len, rdata->result);
tcon->tid, tcon->ses->Suid,
rdata->subreq.start + rdata->subreq.transferred,
rdata->actual_len,
rdata->result);
} else
trace_smb3_read_done(rdata->rreq->debug_id,
rdata->subreq.debug_index,
rdata->xid,
rdata->req->cfile->fid.persistent_fid,
tcon->tid, tcon->ses->Suid,
rdata->subreq.start, rdata->got_bytes);
rdata->subreq.start + rdata->subreq.transferred,
rdata->got_bytes);

if (rdata->result == -ENODATA) {
/* We may have got an EOF error because fallocate
Expand Down Expand Up @@ -4621,6 +4624,7 @@ smb2_async_readv(struct cifs_io_subrequest *rdata)
{
int rc, flags = 0;
char *buf;
struct netfs_io_subrequest *subreq = &rdata->subreq;
struct smb2_hdr *shdr;
struct cifs_io_parms io_parms;
struct smb_rqst rqst = { .rq_iov = rdata->iov,
Expand All @@ -4631,15 +4635,15 @@ smb2_async_readv(struct cifs_io_subrequest *rdata)
int credit_request;

cifs_dbg(FYI, "%s: offset=%llu bytes=%zu\n",
__func__, rdata->subreq.start, rdata->subreq.len);
__func__, subreq->start, subreq->len);

if (!rdata->server)
rdata->server = cifs_pick_channel(tcon->ses);

io_parms.tcon = tlink_tcon(rdata->req->cfile->tlink);
io_parms.server = server = rdata->server;
io_parms.offset = rdata->subreq.start;
io_parms.length = rdata->subreq.len;
io_parms.offset = subreq->start + subreq->transferred;
io_parms.length = rdata->actual_len;
io_parms.persistent_fid = rdata->req->cfile->fid.persistent_fid;
io_parms.volatile_fid = rdata->req->cfile->fid.volatile_fid;
io_parms.pid = rdata->req->pid;
Expand All @@ -4654,11 +4658,13 @@ smb2_async_readv(struct cifs_io_subrequest *rdata)

rdata->iov[0].iov_base = buf;
rdata->iov[0].iov_len = total_len;
rdata->got_bytes = 0;
rdata->result = 0;

shdr = (struct smb2_hdr *)buf;

if (rdata->credits.value > 0) {
shdr->CreditCharge = cpu_to_le16(DIV_ROUND_UP(rdata->subreq.len,
shdr->CreditCharge = cpu_to_le16(DIV_ROUND_UP(rdata->actual_len,
SMB2_MAX_BUFFER_SIZE));
credit_request = le16_to_cpu(shdr->CreditCharge) + 8;
if (server->credits >= server->max_credits)
Expand All @@ -4682,11 +4688,11 @@ smb2_async_readv(struct cifs_io_subrequest *rdata)
if (rc) {
cifs_stats_fail_inc(io_parms.tcon, SMB2_READ_HE);
trace_smb3_read_err(rdata->rreq->debug_id,
rdata->subreq.debug_index,
subreq->debug_index,
rdata->xid, io_parms.persistent_fid,
io_parms.tcon->tid,
io_parms.tcon->ses->Suid,
io_parms.offset, io_parms.length, rc);
io_parms.offset, rdata->actual_len, rc);
}

async_readv_out:
Expand Down
1 change: 1 addition & 0 deletions fs/smb/client/trace.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
EM(cifs_trace_rw_credits_old_session, "old-session") \
EM(cifs_trace_rw_credits_read_response_add, "rd-resp-add") \
EM(cifs_trace_rw_credits_read_response_clear, "rd-resp-clr") \
EM(cifs_trace_rw_credits_read_resubmit, "rd-resubmit") \
EM(cifs_trace_rw_credits_read_submit, "rd-submit ") \
EM(cifs_trace_rw_credits_write_prepare, "wr-prepare ") \
EM(cifs_trace_rw_credits_write_response_add, "wr-resp-add") \
Expand Down

0 comments on commit 6a5dcd4

Please sign in to comment.