Skip to content

Commit

Permalink
netfs: Implement buffered writes through netfs_file_write_iter()
Browse files Browse the repository at this point in the history
Institute a netfs write helper, netfs_file_write_iter(), to be pointed at
by the network filesystem ->write_iter() call.  Make it handled buffered
writes by copying the data into the pagecache and creating a dirty regions
to represent contiguous spans of compatible data.

Dirty regions are considered compatible, and thus mergeable, if they are
part of the same flush group, have the same fscache requirements and have
the same O_SYNC/O_DSYNC characteristics - plus any constraints the netfs
wishes to add.

Signed-off-by: David Howells <dhowells@redhat.com>
  • Loading branch information
dhowells committed Feb 15, 2022
1 parent 619aecf commit 7de6bd1
Show file tree
Hide file tree
Showing 6 changed files with 958 additions and 15 deletions.
3 changes: 2 additions & 1 deletion fs/netfs/Makefile
Expand Up @@ -4,7 +4,8 @@ netfs-y := \
direct.o \
misc.o \
objects.o \
read_helper.o
read_helper.o \
write_helper.o

netfs-$(CONFIG_NETFS_STATS) += stats.o

Expand Down
28 changes: 28 additions & 0 deletions fs/netfs/internal.h
Expand Up @@ -15,6 +15,26 @@

#define pr_fmt(fmt) "netfs: " fmt

static inline void *netfs_mas_set_flushing(struct netfs_dirty_region *region)
{
return (void *)((unsigned long)region | 0x2UL);
}

static inline bool netfs_mas_is_flushing(const void *mas_entry)
{
return (unsigned long)mas_entry & 2UL;
}

/*
* Return true if the pointer is a valid region pointer - ie. not
* NULL, XA_ZERO_ENTRY, NETFS_*_TO_CACHE or a flushing entry.
*/
static inline bool netfs_mas_is_valid(const void *mas_entry)
{
return (unsigned long)mas_entry > 0x8000UL &&
!netfs_mas_is_flushing(mas_entry);
}

/*
* misc.c
*/
Expand Down Expand Up @@ -57,6 +77,7 @@ void netfs_get_read_request(struct netfs_read_request *rreq);
void netfs_put_read_request(struct netfs_read_request *rreq, bool was_async);
void netfs_rreq_completed(struct netfs_read_request *rreq, bool was_async);
void netfs_rreq_assess(struct netfs_read_request *rreq, bool was_async);
int netfs_prefetch_for_write(struct file *file, struct folio *folio, size_t len);

static inline void netfs_put_subrequest(struct netfs_read_subrequest *subreq,
bool was_async)
Expand All @@ -65,6 +86,13 @@ static inline void netfs_put_subrequest(struct netfs_read_subrequest *subreq,
__netfs_put_subrequest(subreq, was_async);
}

/*
* write_helper.c
*/
bool netfs_are_regions_mergeable(struct netfs_i_context *ctx,
struct netfs_dirty_region *a,
struct netfs_dirty_region *b);

/*
* stats.c
*/
Expand Down
129 changes: 116 additions & 13 deletions fs/netfs/read_helper.c
Expand Up @@ -1075,6 +1075,7 @@ EXPORT_SYMBOL(netfs_readpage);

/*
* Prepare a folio for writing without reading first
* @ctx: File context
* @folio: The folio being prepared
* @pos: starting position for the write
* @len: length of write
Expand All @@ -1088,32 +1089,41 @@ EXPORT_SYMBOL(netfs_readpage);
* If any of these criteria are met, then zero out the unwritten parts
* of the folio and return true. Otherwise, return false.
*/
static bool netfs_skip_folio_read(struct folio *folio, loff_t pos, size_t len,
bool always_fill)
static bool netfs_skip_folio_read(struct netfs_i_context *ctx, struct folio *folio,
loff_t pos, size_t len, bool always_fill)
{
struct inode *inode = folio_inode(folio);
loff_t i_size = i_size_read(inode);
loff_t i_size = i_size_read(inode), low, high;
size_t offset = offset_in_folio(folio, pos);
size_t plen = folio_size(folio);
size_t min_bsize = 1UL << ctx->min_bshift;

if (likely(min_bsize == 1)) {
low = folio_file_pos(folio);
high = low + plen;
} else {
low = round_down(pos, min_bsize);
high = round_up(pos + len, min_bsize);
}

if (unlikely(always_fill)) {
if (pos - offset + len <= i_size)
return false; /* Page entirely before EOF */
if (low < i_size)
return false; /* Some part of the block before EOF */
zero_user_segment(&folio->page, 0, plen);
folio_mark_uptodate(folio);
return true;
}

/* Full folio write */
if (offset == 0 && len >= plen)
/* Full page write */
if (pos == low && high == pos + len)
return true;

/* Page entirely beyond the end of the file */
if (pos - offset >= i_size)
/* pos beyond last page in the file */
if (low >= i_size)
goto zero_out;

/* Write that covers from the start of the folio to EOF or beyond */
if (offset == 0 && (pos + len) >= i_size)
if (pos == low && (pos + len) >= i_size)
goto zero_out;

return false;
Expand Down Expand Up @@ -1195,13 +1205,14 @@ int netfs_write_begin(struct file *file, struct address_space *mapping,
* to preload the granule.
*/
if (!netfs_is_cache_enabled(ctx) &&
netfs_skip_folio_read(folio, pos, len, false)) {
netfs_skip_folio_read(ctx, folio, pos, len, false)) {
netfs_stat(&netfs_n_rh_write_zskip);
goto have_folio_no_wait;
}

rreq = netfs_alloc_read_request(mapping, file, folio_file_pos(folio),
folio_size(folio), NETFS_READ_FOR_WRITE);
rreq = netfs_alloc_read_request(mapping, file,
folio_file_pos(folio),folio_size(folio),
NETFS_READ_FOR_WRITE);
if (IS_ERR(rreq)) {
ret = PTR_ERR(rreq);
goto error;
Expand Down Expand Up @@ -1282,3 +1293,95 @@ int netfs_write_begin(struct file *file, struct address_space *mapping,
return ret;
}
EXPORT_SYMBOL(netfs_write_begin);

/*
* Preload the data into a page we're proposing to write into.
*/
int netfs_prefetch_for_write(struct file *file, struct folio *folio, size_t len)
{
struct netfs_read_request *rreq;
struct address_space *mapping = folio_file_mapping(folio);
struct netfs_i_context *ctx = netfs_i_context(mapping->host);
unsigned long long i_size, end;
unsigned int debug_index = 0;
loff_t rstart, pos = folio_pos(folio);
size_t rlen;
int ret;

DEFINE_READAHEAD(ractl, file, NULL, mapping, folio_index(folio));

_enter("%zx @%llx", len, pos);

ret = -ENOMEM;

i_size = i_size_read(mapping->host);
end = round_up(pos + len, 1U << ctx->min_bshift);
if (end > i_size) {
unsigned long long limit = round_up(pos + len, PAGE_SIZE);
end = max(limit, round_up(i_size, PAGE_SIZE));
}
rstart = round_down(pos, 1U << ctx->min_bshift);
rlen = end - rstart;

rreq = netfs_alloc_read_request(mapping, file, rstart, rlen,
NETFS_READ_FOR_WRITE);
if (!rreq)
goto error;

rreq->no_unlock_folio = folio_index(folio);
__set_bit(NETFS_RREQ_NO_UNLOCK_FOLIO, &rreq->flags);
ret = netfs_begin_cache_operation(rreq, ctx);
if (ret == -ENOMEM || ret == -EINTR || ret == -ERESTARTSYS)
goto error_put;

netfs_stat(&netfs_n_rh_write_begin);
trace_netfs_read(rreq, pos, len, netfs_read_trace_prefetch_for_write);

/* Expand the request to meet caching requirements and download
* preferences.
*/
ractl._nr_pages = folio_nr_pages(folio);
netfs_rreq_expand(rreq, &ractl);

/* Set up the output buffer */
ret = netfs_set_up_buffer(&rreq->buffer, rreq->mapping, &ractl, folio,
readahead_index(&ractl), readahead_count(&ractl));
if (ret < 0) {
folio_get(folio);
while (readahead_folio(&ractl))
;
goto error_put;
}

netfs_get_read_request(rreq);
atomic_set(&rreq->nr_rd_ops, 1);
do {
if (!netfs_rreq_submit_slice(rreq, &debug_index))
break;

} while (rreq->submitted < rreq->len);

/* Keep nr_rd_ops incremented so that the ref always belongs to us, and
* the service code isn't punted off to a random thread pool to
* process.
*/
for (;;) {
wait_var_event(&rreq->nr_rd_ops, atomic_read(&rreq->nr_rd_ops) == 1);
netfs_rreq_assess(rreq, false);
if (!test_bit(NETFS_RREQ_IN_PROGRESS, &rreq->flags))
break;
cond_resched();
}

ret = rreq->error;
if (ret == 0 && rreq->submitted < rreq->len) {
trace_netfs_failure(rreq, NULL, ret, netfs_fail_short_write_begin);
ret = -EIO;
}

error_put:
netfs_put_read_request(rreq, false);
error:
_leave(" = %d", ret);
return ret;
}

0 comments on commit 7de6bd1

Please sign in to comment.