blob: 67bf7c764cae8b8716d26fa1223c535df90da5ff [file] [edit]
// SPDX-License-Identifier: GPL-2.0-only
/* Network filesystem write retrying.
*
* Copyright (C) 2024 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*/
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include "internal.h"
/*
* Prepare the write buffer for a retry. We can't necessarily reuse the write
* buffer from the previous run of a subrequest because the filesystem is
* permitted to modify it (add headers/trailers, encrypt it). Further, the
* subrequest may now be a different size (e.g. cifs has to negotiate for
* maximum transfer size). Also, we can't look at *stream as that may still
* refer to the source material being broken up into original subrequests.
*/
int netfs_prepare_write_retry_buffer(struct netfs_io_subrequest *subreq,
unsigned int max_segs)
{
struct netfs_io_request *wreq = subreq->rreq;
size_t len;
bvecq_pos_set(&subreq->dispatch_pos, &wreq->retry_cursor);
bvecq_pos_set(&subreq->content, &wreq->retry_cursor);
len = bvecq_slice(&wreq->retry_cursor, subreq->len, max_segs, &subreq->nr_segs);
if (len < subreq->len) {
subreq->len = len;
trace_netfs_sreq(subreq, netfs_sreq_trace_limited);
}
wreq->retry_start += len;
wreq->retry_buffered -= len;
if (wreq->retry_buffered == 0)
bvecq_pos_unset(&wreq->retry_cursor);
return 0;
}
/*
* Perform retries on the streams that need it. This only has to deal with
* buffered writes; unbuffered write retry is handled in direct_write.c.
*/
static void netfs_retry_write_stream(struct netfs_io_request *wreq,
struct netfs_io_stream *stream)
{
struct list_head *next;
int ret;
_enter("R=%x[%x:]", wreq->debug_id, stream->stream_nr);
if (list_empty(&stream->subrequests))
return;
if (WARN_ON_ONCE(stream->source != NETFS_UPLOAD_TO_SERVER))
return; /* Shouldn't be retrying cache writes. */
if (wreq->netfs_ops->retry_request)
wreq->netfs_ops->retry_request(wreq, stream);
if (unlikely(stream->failed))
return;
/* Read pointer to subreq before reading subreq state. */
next = smp_load_acquire(&stream->subrequests.next);
do {
struct netfs_io_subrequest *subreq = NULL, *from, *to, *tmp;
uoff_t start, len;
bool subreq_superfluous = false;
bvecq_pos_unset(&wreq->retry_cursor);
/* Go through the stream and find the next span of contiguous
* data that we then rejig (cifs, for example, needs the wsize
* renegotiating) and reissue.
*/
from = list_entry(next, struct netfs_io_subrequest, rreq_link);
to = from;
start = from->start + from->transferred;
len = from->len - from->transferred;
if (test_bit(NETFS_SREQ_FAILED, &from->flags) ||
!test_bit(NETFS_SREQ_NEED_RETRY, &from->flags))
goto out;
for (;;) {
/* Read pointer to subreq before reading subreq state. */
next = smp_load_acquire(&next->next);
if (next == &stream->subrequests)
break;
subreq = list_entry(next, struct netfs_io_subrequest, rreq_link);
if (subreq->start != start + len ||
subreq->transferred > 0 ||
!test_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags))
break;
to = subreq;
len += to->len;
}
/* Determine the set of buffers we're going to use. Each
* subreq gets a subset of a single overall contiguous buffer.
*/
bvecq_pos_transfer(&wreq->retry_cursor, &from->dispatch_pos);
bvecq_pos_advance(&wreq->retry_cursor, from->transferred);
wreq->retry_start = start;
wreq->retry_buffered = len;
/* Work through the sublist. The chain of buffers we're going
* to fill is attached to dispatch_cursor and we need to read
* 'len' amount of data from 'start'.
*/
subreq = from;
list_for_each_entry_from(subreq, &stream->subrequests, rreq_link) {
if (!wreq->retry_buffered) {
subreq_superfluous = true;
break;
}
bvecq_pos_unset(&subreq->content);
bvecq_pos_unset(&subreq->dispatch_pos);
__clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
__clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
__clear_bit(NETFS_SREQ_FAILED, &subreq->flags);
__set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags);
subreq->start = wreq->retry_start;
subreq->len = wreq->retry_buffered;
subreq->transferred = 0;
subreq->retry_count += 1;
subreq->error = 0;
netfs_stat(&netfs_n_wh_retry_write_subreq);
trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
ret = stream->issue_write(subreq);
if (ret < 0) {
/* Ownership of subreq was returned to us.
* Expand the subreq to consume the entire
* remaining amount to be retried and fail it.
*/
trace_netfs_sreq(subreq, netfs_sreq_trace_fail);
wreq->retry_buffered -= subreq->len;
netfs_write_subrequest_terminated(subreq, ret);
break;
}
if (subreq == to) {
subreq_superfluous = false;
break;
}
}
/* If we managed to use fewer subreqs, we can discard the
* excess; if we used the same number, then we're done.
*/
if (!wreq->retry_buffered) {
if (!subreq_superfluous)
continue;
list_for_each_entry_safe_from(subreq, tmp,
&stream->subrequests, rreq_link) {
trace_netfs_sreq(subreq, netfs_sreq_trace_discard);
spin_lock(&wreq->lock);
list_del(&subreq->rreq_link);
spin_unlock(&wreq->lock);
netfs_put_subrequest(subreq, netfs_sreq_trace_put_done);
if (subreq == to)
break;
}
continue;
}
/* We ran out of subrequests, so we need to allocate some more
* and insert them after.
*/
do {
subreq = netfs_alloc_subrequest(wreq, stream->source);
subreq->start = start;
subreq->len = wreq->retry_buffered;
subreq->stream_nr = to->stream_nr;
subreq->retry_count = 1;
trace_netfs_sreq_ref(wreq->debug_id, subreq->debug_index,
refcount_read(&subreq->ref),
netfs_sreq_trace_new);
trace_netfs_sreq(subreq, netfs_sreq_trace_split);
__set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags);
spin_lock(&wreq->lock);
/* Write IN_PROGRESS before pointer to new subreq */
smp_wmb();
list_add(&subreq->rreq_link, &to->rreq_link);
spin_unlock(&wreq->lock);
to = subreq;
switch (stream->source) {
case NETFS_UPLOAD_TO_SERVER:
netfs_stat(&netfs_n_wh_upload);
break;
default:
WARN_ON_ONCE(1);
}
trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
ret = stream->issue_write(subreq);
if (ret < 0) {
/* Ownership of subreq was returned to us.
* Expand the subreq to consume the entire
* remaining amount to be retried and fail it.
*/
trace_netfs_sreq(subreq, netfs_sreq_trace_fail);
wreq->retry_buffered -= subreq->len;
netfs_write_subrequest_terminated(subreq, ret);
break;
}
} while (wreq->retry_buffered > 0);
} while (!list_is_head(next, &stream->subrequests));
out:
bvecq_pos_unset(&wreq->retry_cursor);
}
/*
* Perform retries on the streams that need it. If we're doing content
* encryption and the server copy changed due to a third-party write, we may
* need to do an RMW cycle and also rewrite the data to the cache.
*/
void netfs_retry_writes(struct netfs_io_request *wreq)
{
struct netfs_io_stream *stream;
int s;
netfs_stat(&netfs_n_wh_retry_write_req);
trace_netfs_rreq(wreq, netfs_rreq_trace_retry_begin);
/* Wait for all outstanding I/O to quiesce before performing retries as
* we may need to renegotiate the I/O sizes.
*/
set_bit(NETFS_RREQ_RETRYING, &wreq->flags);
for (s = 0; s < NR_IO_STREAMS; s++) {
stream = &wreq->io_streams[s];
if (stream->active)
netfs_wait_for_in_progress_stream(wreq, stream);
}
clear_bit(NETFS_RREQ_RETRYING, &wreq->flags);
// TODO: Enc: Fetch changed partial pages
// TODO: Enc: Reencrypt content if needed.
// TODO: Enc: Wind back transferred point.
// TODO: Enc: Mark cache pages for retry.
for (s = 0; s < NR_IO_STREAMS; s++) {
stream = &wreq->io_streams[s];
if (stream->need_retry) {
stream->need_retry = false;
netfs_retry_write_stream(wreq, stream);
}
}
trace_netfs_rreq(wreq, netfs_rreq_trace_retry_end);
}