Skip to content

Commit 41d8e76

Browse files
committed
netfs: Implement a write-through caching option
Provide a flag whereby a filesystem may request that cifs_perform_write() perform write-through caching. This involves putting pages directly into writeback rather than dirty and attaching them to a write operation as we go. Further, the writes being made are limited to the byte range being written rather than whole folios being written. This can be used by cifs, for example, to deal with strict byte-range locking. This can't be used with content encryption as that may require expansion of the write RPC beyond the write being made. This doesn't affect writes via mmap - those are written back in the normal way; similarly failed writethrough writes are marked dirty and left to writeback to retry. Another option would be to simply invalidate them, but the contents can be simultaneously accessed by read() and through mmap. Signed-off-by: David Howells <dhowells@redhat.com> cc: Jeff Layton <jlayton@kernel.org> cc: linux-cachefs@redhat.com cc: linux-fsdevel@vger.kernel.org cc: linux-mm@kvack.org
1 parent 4a79616 commit 41d8e76

File tree

7 files changed

+162
-12
lines changed

7 files changed

+162
-12
lines changed

fs/netfs/buffered_write.c

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ enum netfs_how_to_modify {
2626
NETFS_FLUSH_CONTENT, /* Flush incompatible content. */
2727
};
2828

29+
static void netfs_cleanup_buffered_write(struct netfs_io_request *wreq);
30+
2931
static void netfs_set_group(struct folio *folio, struct netfs_group *netfs_group)
3032
{
3133
if (netfs_group && !folio_get_private(folio))
@@ -133,6 +135,14 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter,
133135
struct inode *inode = file_inode(file);
134136
struct address_space *mapping = inode->i_mapping;
135137
struct netfs_inode *ctx = netfs_inode(inode);
138+
struct writeback_control wbc = {
139+
.sync_mode = WB_SYNC_NONE,
140+
.for_sync = true,
141+
.nr_to_write = LONG_MAX,
142+
.range_start = iocb->ki_pos,
143+
.range_end = iocb->ki_pos + iter->count,
144+
};
145+
struct netfs_io_request *wreq = NULL;
136146
struct netfs_folio *finfo;
137147
struct folio *folio;
138148
enum netfs_how_to_modify howto;
@@ -143,6 +153,30 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter,
143153
size_t max_chunk = PAGE_SIZE << MAX_PAGECACHE_ORDER;
144154
bool maybe_trouble = false;
145155

156+
if (unlikely(test_bit(NETFS_ICTX_WRITETHROUGH, &ctx->flags) ||
157+
iocb->ki_flags & (IOCB_DSYNC | IOCB_SYNC))
158+
) {
159+
if (pos < i_size_read(inode)) {
160+
ret = filemap_write_and_wait_range(mapping, pos, pos + iter->count);
161+
if (ret < 0) {
162+
goto out;
163+
}
164+
}
165+
166+
wbc_attach_fdatawrite_inode(&wbc, mapping->host);
167+
168+
wreq = netfs_begin_writethrough(iocb, iter->count);
169+
if (IS_ERR(wreq)) {
170+
wbc_detach_inode(&wbc);
171+
ret = PTR_ERR(wreq);
172+
wreq = NULL;
173+
goto out;
174+
}
175+
if (!is_sync_kiocb(iocb))
176+
wreq->iocb = iocb;
177+
wreq->cleanup = netfs_cleanup_buffered_write;
178+
}
179+
146180
do {
147181
size_t flen;
148182
size_t offset; /* Offset into pagecache folio */
@@ -315,7 +349,25 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter,
315349
}
316350
written += copied;
317351

318-
folio_mark_dirty(folio);
352+
if (likely(!wreq)) {
353+
folio_mark_dirty(folio);
354+
} else {
355+
if (folio_test_dirty(folio))
356+
/* Sigh. mmap. */
357+
folio_clear_dirty_for_io(folio);
358+
/* We make multiple writes to the folio... */
359+
if (!folio_test_writeback(folio)) {
360+
folio_wait_fscache(folio);
361+
folio_start_writeback(folio);
362+
folio_start_fscache(folio);
363+
if (wreq->iter.count == 0)
364+
trace_netfs_folio(folio, netfs_folio_trace_wthru);
365+
else
366+
trace_netfs_folio(folio, netfs_folio_trace_wthru_plus);
367+
}
368+
netfs_advance_writethrough(wreq, copied,
369+
offset + copied == flen);
370+
}
319371
retry:
320372
folio_unlock(folio);
321373
folio_put(folio);
@@ -325,17 +377,14 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter,
325377
} while (iov_iter_count(iter));
326378

327379
out:
328-
if (likely(written)) {
329-
/* Flush and wait for a write that requires immediate synchronisation. */
330-
if (iocb->ki_flags & (IOCB_DSYNC | IOCB_SYNC)) {
331-
_debug("dsync");
332-
ret = filemap_fdatawait_range(mapping, iocb->ki_pos,
333-
iocb->ki_pos + written);
334-
}
335-
336-
iocb->ki_pos += written;
380+
if (unlikely(wreq)) {
381+
ret = netfs_end_writethrough(wreq, iocb);
382+
wbc_detach_inode(&wbc);
383+
if (ret == -EIOCBQUEUED)
384+
return ret;
337385
}
338386

387+
iocb->ki_pos += written;
339388
_leave(" = %zd [%zd]", written, ret);
340389
return written ? written : ret;
341390

fs/netfs/internal.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ static inline void netfs_see_request(struct netfs_io_request *rreq,
101101
*/
102102
int netfs_begin_write(struct netfs_io_request *wreq, bool may_wait,
103103
enum netfs_write_trace what);
104+
struct netfs_io_request *netfs_begin_writethrough(struct kiocb *iocb, size_t len);
105+
int netfs_advance_writethrough(struct netfs_io_request *wreq, size_t copied, bool to_page_end);
106+
int netfs_end_writethrough(struct netfs_io_request *wreq, struct kiocb *iocb);
104107

105108
/*
106109
* stats.c

fs/netfs/main.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ static const char *netfs_origins[nr__netfs_io_origin] = {
3030
[NETFS_READPAGE] = "RP",
3131
[NETFS_READ_FOR_WRITE] = "RW",
3232
[NETFS_WRITEBACK] = "WB",
33+
[NETFS_WRITETHROUGH] = "WT",
3334
[NETFS_LAUNDER_WRITE] = "LW",
3435
[NETFS_UNBUFFERED_WRITE] = "UW",
3536
[NETFS_DIO_READ] = "DR",

fs/netfs/objects.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ struct netfs_io_request *netfs_alloc_request(struct address_space *mapping,
4141
rreq->i_size = i_size_read(inode);
4242
rreq->debug_id = atomic_inc_return(&debug_ids);
4343
INIT_LIST_HEAD(&rreq->subrequests);
44+
INIT_WORK(&rreq->work, NULL);
4445
refcount_set(&rreq->ref, 1);
4546

4647
__set_bit(NETFS_RREQ_IN_PROGRESS, &rreq->flags);

fs/netfs/output.c

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,3 +386,93 @@ int netfs_begin_write(struct netfs_io_request *wreq, bool may_wait,
386386
TASK_UNINTERRUPTIBLE);
387387
return wreq->error;
388388
}
389+
390+
/*
391+
* Begin a write operation for writing through the pagecache.
392+
*/
393+
struct netfs_io_request *netfs_begin_writethrough(struct kiocb *iocb, size_t len)
394+
{
395+
struct netfs_io_request *wreq;
396+
struct file *file = iocb->ki_filp;
397+
398+
wreq = netfs_alloc_request(file->f_mapping, file, iocb->ki_pos, len,
399+
NETFS_WRITETHROUGH);
400+
if (IS_ERR(wreq))
401+
return wreq;
402+
403+
trace_netfs_write(wreq, netfs_write_trace_writethrough);
404+
405+
__set_bit(NETFS_RREQ_UPLOAD_TO_SERVER, &wreq->flags);
406+
iov_iter_xarray(&wreq->iter, ITER_SOURCE, &wreq->mapping->i_pages, wreq->start, 0);
407+
wreq->io_iter = wreq->iter;
408+
409+
/* ->outstanding > 0 carries a ref */
410+
netfs_get_request(wreq, netfs_rreq_trace_get_for_outstanding);
411+
atomic_set(&wreq->nr_outstanding, 1);
412+
return wreq;
413+
}
414+
415+
static void netfs_submit_writethrough(struct netfs_io_request *wreq, bool final)
416+
{
417+
struct netfs_inode *ictx = netfs_inode(wreq->inode);
418+
unsigned long long start;
419+
size_t len;
420+
421+
if (!test_bit(NETFS_RREQ_UPLOAD_TO_SERVER, &wreq->flags))
422+
return;
423+
424+
start = wreq->start + wreq->submitted;
425+
len = wreq->iter.count - wreq->submitted;
426+
if (!final) {
427+
len /= wreq->wsize; /* Round to number of maximum packets */
428+
len *= wreq->wsize;
429+
}
430+
431+
ictx->ops->create_write_requests(wreq, start, len);
432+
wreq->submitted += len;
433+
}
434+
435+
/*
436+
* Advance the state of the write operation used when writing through the
437+
* pagecache. Data has been copied into the pagecache that we need to append
438+
* to the request. If we've added more than wsize then we need to create a new
439+
* subrequest.
440+
*/
441+
int netfs_advance_writethrough(struct netfs_io_request *wreq, size_t copied, bool to_page_end)
442+
{
443+
_enter("ic=%zu sb=%zu ws=%u cp=%zu tp=%u",
444+
wreq->iter.count, wreq->submitted, wreq->wsize, copied, to_page_end);
445+
446+
wreq->iter.count += copied;
447+
wreq->io_iter.count += copied;
448+
if (to_page_end && wreq->io_iter.count - wreq->submitted >= wreq->wsize)
449+
netfs_submit_writethrough(wreq, false);
450+
451+
return wreq->error;
452+
}
453+
454+
/*
455+
* End a write operation used when writing through the pagecache.
456+
*/
457+
int netfs_end_writethrough(struct netfs_io_request *wreq, struct kiocb *iocb)
458+
{
459+
int ret = -EIOCBQUEUED;
460+
461+
_enter("ic=%zu sb=%zu ws=%u",
462+
wreq->iter.count, wreq->submitted, wreq->wsize);
463+
464+
if (wreq->submitted < wreq->io_iter.count)
465+
netfs_submit_writethrough(wreq, true);
466+
467+
if (atomic_dec_and_test(&wreq->nr_outstanding))
468+
netfs_write_terminated(wreq, false);
469+
470+
if (is_sync_kiocb(iocb)) {
471+
wait_on_bit(&wreq->flags, NETFS_RREQ_IN_PROGRESS,
472+
TASK_UNINTERRUPTIBLE);
473+
ret = wreq->error;
474+
}
475+
476+
netfs_put_request(wreq, false, netfs_rreq_trace_put_return);
477+
return ret;
478+
}

include/linux/netfs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ struct netfs_inode {
139139
unsigned long flags;
140140
#define NETFS_ICTX_ODIRECT 0 /* The file has DIO in progress */
141141
#define NETFS_ICTX_UNBUFFERED 1 /* I/O should not use the pagecache */
142+
#define NETFS_ICTX_WRITETHROUGH 2 /* Write-through caching */
142143
};
143144

144145
/*
@@ -227,6 +228,7 @@ enum netfs_io_origin {
227228
NETFS_READPAGE, /* This read is a synchronous read */
228229
NETFS_READ_FOR_WRITE, /* This read is to prepare a write */
229230
NETFS_WRITEBACK, /* This write was triggered by writepages */
231+
NETFS_WRITETHROUGH, /* This write was made by netfs_perform_write() */
230232
NETFS_LAUNDER_WRITE, /* This is triggered by ->launder_folio() */
231233
NETFS_UNBUFFERED_WRITE, /* This is an unbuffered write */
232234
NETFS_DIO_READ, /* This is a direct I/O read */

include/trace/events/netfs.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,15 @@
2727
EM(netfs_write_trace_dio_write, "DIO-WRITE") \
2828
EM(netfs_write_trace_launder, "LAUNDER ") \
2929
EM(netfs_write_trace_unbuffered_write, "UNB-WRITE") \
30-
E_(netfs_write_trace_writeback, "WRITEBACK")
30+
EM(netfs_write_trace_writeback, "WRITEBACK") \
31+
E_(netfs_write_trace_writethrough, "WRITETHRU")
3132

3233
#define netfs_rreq_origins \
3334
EM(NETFS_READAHEAD, "RA") \
3435
EM(NETFS_READPAGE, "RP") \
3536
EM(NETFS_READ_FOR_WRITE, "RW") \
3637
EM(NETFS_WRITEBACK, "WB") \
38+
EM(NETFS_WRITETHROUGH, "WT") \
3739
EM(NETFS_LAUNDER_WRITE, "LW") \
3840
EM(NETFS_UNBUFFERED_WRITE, "UW") \
3941
EM(NETFS_DIO_READ, "DR") \
@@ -136,7 +138,9 @@
136138
EM(netfs_folio_trace_redirty, "redirty") \
137139
EM(netfs_folio_trace_redirtied, "redirtied") \
138140
EM(netfs_folio_trace_store, "store") \
139-
E_(netfs_folio_trace_store_plus, "store+")
141+
EM(netfs_folio_trace_store_plus, "store+") \
142+
EM(netfs_folio_trace_wthru, "wthru") \
143+
E_(netfs_folio_trace_wthru_plus, "wthru+")
140144

141145
#ifndef __NETFS_DECLARE_TRACE_ENUMS_ONCE_ONLY
142146
#define __NETFS_DECLARE_TRACE_ENUMS_ONCE_ONLY

0 commit comments

Comments
 (0)