Skip to content
Permalink
Browse files
netfs: Implement buffered writes through netfs_file_write_iter()
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 10, 2022
1 parent 4638cb2 commit 01abe2ebb0b5676c9d0394947cfeea45d0f5c09f
Show file tree
Hide file tree
Showing 10 changed files with 1,198 additions and 203 deletions.
@@ -31,7 +31,7 @@ const struct file_operations afs_file_operations = {
.release = afs_release,
.llseek = generic_file_llseek,
.read_iter = afs_file_read_iter,
.write_iter = afs_file_write,
.write_iter = netfs_file_write_iter,
.mmap = afs_file_mmap,
.splice_read = generic_file_splice_read,
.splice_write = iter_file_splice_write,
@@ -54,8 +54,6 @@ const struct address_space_operations afs_file_aops = {
.launder_page = afs_launder_page,
.releasepage = netfs_releasepage,
.invalidatepage = netfs_invalidatepage,
.write_begin = afs_write_begin,
.write_end = afs_write_end,
.writepage = afs_writepage,
.writepages = afs_writepages,
};
@@ -371,11 +369,51 @@ static void afs_priv_cleanup(struct address_space *mapping, void *netfs_priv)
key_put(netfs_priv);
}

static void afs_init_dirty_region(struct netfs_dirty_region *region, struct file *file)
{
region->netfs_priv = key_get(afs_file_key(file));
}

static void afs_split_dirty_region(struct netfs_dirty_region *region)
{
key_get(region->netfs_priv);
}

static void afs_free_dirty_region(struct netfs_dirty_region *region)
{
key_put(region->netfs_priv);
}

static void afs_update_i_size(struct file *file, loff_t new_i_size)
{
struct afs_vnode *vnode = AFS_FS_I(file_inode(file));
loff_t i_size;

write_seqlock(&vnode->cb_lock);
i_size = i_size_read(&vnode->vfs_inode);
if (new_i_size > i_size) {
i_size_write(&vnode->vfs_inode, new_i_size);
inode_set_bytes(&vnode->vfs_inode, new_i_size);
}
write_sequnlock(&vnode->cb_lock);
fscache_update_cookie(afs_vnode_cache(vnode), NULL, &new_i_size);
}

static int afs_validate_for_write(struct inode *inode, struct file *file)
{
return afs_validate(AFS_FS_I(inode), afs_file_key(file));
}

const struct netfs_request_ops afs_req_ops = {
.init_rreq = afs_init_rreq,
.check_write_begin = afs_check_write_begin,
.issue_op = afs_req_issue_op,
.cleanup = afs_priv_cleanup,
.init_dirty_region = afs_init_dirty_region,
.split_dirty_region = afs_split_dirty_region,
.free_dirty_region = afs_free_dirty_region,
.update_i_size = afs_update_i_size,
.validate_for_write = afs_validate_for_write,
};

int afs_write_inode(struct inode *inode, struct writeback_control *wbc)
@@ -1533,15 +1533,8 @@ extern int afs_set_page_dirty(struct page *);
#else
#define afs_set_page_dirty __set_page_dirty_nobuffers
#endif
extern int afs_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **pagep, void **fsdata);
extern int afs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *page, void *fsdata);
extern int afs_writepage(struct page *, struct writeback_control *);
extern int afs_writepages(struct address_space *, struct writeback_control *);
extern ssize_t afs_file_write(struct kiocb *, struct iov_iter *);
extern int afs_fsync(struct file *, loff_t, loff_t, int);
extern vm_fault_t afs_page_mkwrite(struct vm_fault *vmf);
extern void afs_prune_wb_keys(struct afs_vnode *);
@@ -37,150 +37,6 @@ static void afs_folio_start_fscache(bool caching, struct folio *folio)
}
#endif

/*
* prepare to perform part of a write to a page
*/
int afs_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **_page, void **fsdata)
{
struct afs_vnode *vnode = AFS_FS_I(file_inode(file));
struct folio *folio;
unsigned long priv;
unsigned f, from;
unsigned t, to;
pgoff_t index;
int ret;

_enter("{%llx:%llu},%llx,%x",
vnode->fid.vid, vnode->fid.vnode, pos, len);

/* Prefetch area to be written into the cache if we're caching this
* file. We need to do this before we get a lock on the page in case
* there's more than one writer competing for the same cache block.
*/
ret = netfs_write_begin(file, mapping, pos, len, flags, &folio, fsdata);
if (ret < 0)
return ret;

index = folio_index(folio);
from = pos - index * PAGE_SIZE;
to = from + len;

try_again:
/* See if this page is already partially written in a way that we can
* merge the new write with.
*/
if (folio_test_private(folio)) {
priv = (unsigned long)folio_get_private(folio);
f = afs_folio_dirty_from(folio, priv);
t = afs_folio_dirty_to(folio, priv);
ASSERTCMP(f, <=, t);

if (folio_test_writeback(folio)) {
trace_afs_folio_dirty(vnode, tracepoint_string("alrdy"), folio);
goto flush_conflicting_write;
}
/* If the file is being filled locally, allow inter-write
* spaces to be merged into writes. If it's not, only write
* back what the user gives us.
*/
if (!test_bit(NETFS_ICTX_NEW_CONTENT, &vnode->netfs_ctx.flags) &&
(to < f || from > t))
goto flush_conflicting_write;
}

*_page = &folio->page;
_leave(" = 0");
return 0;

/* The previous write and this write aren't adjacent or overlapping, so
* flush the page out.
*/
flush_conflicting_write:
_debug("flush conflict");
ret = folio_write_one(folio);
if (ret < 0)
goto error;

ret = folio_lock_killable(folio);
if (ret < 0)
goto error;
goto try_again;

error:
folio_put(folio);
_leave(" = %d", ret);
return ret;
}

/*
* finalise part of a write to a page
*/
int afs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *subpage, void *fsdata)
{
struct folio *folio = page_folio(subpage);
struct afs_vnode *vnode = AFS_FS_I(file_inode(file));
unsigned long priv;
unsigned int f, from = offset_in_folio(folio, pos);
unsigned int t, to = from + copied;
loff_t i_size, write_end_pos;

_enter("{%llx:%llu},{%lx}",
vnode->fid.vid, vnode->fid.vnode, folio_index(folio));

if (!folio_test_uptodate(folio)) {
if (copied < len) {
copied = 0;
goto out;
}

folio_mark_uptodate(folio);
}

if (copied == 0)
goto out;

write_end_pos = pos + copied;

i_size = i_size_read(&vnode->vfs_inode);
if (write_end_pos > i_size) {
write_seqlock(&vnode->cb_lock);
i_size = i_size_read(&vnode->vfs_inode);
if (write_end_pos > i_size)
afs_set_i_size(vnode, write_end_pos);
write_sequnlock(&vnode->cb_lock);
fscache_update_cookie(afs_vnode_cache(vnode), NULL, &write_end_pos);
}

if (folio_test_private(folio)) {
priv = (unsigned long)folio_get_private(folio);
f = afs_folio_dirty_from(folio, priv);
t = afs_folio_dirty_to(folio, priv);
if (from < f)
f = from;
if (to > t)
t = to;
priv = afs_folio_dirty(folio, f, t);
folio_change_private(folio, (void *)priv);
trace_afs_folio_dirty(vnode, tracepoint_string("dirty+"), folio);
} else {
priv = afs_folio_dirty(folio, from, to);
folio_attach_private(folio, (void *)priv);
trace_afs_folio_dirty(vnode, tracepoint_string("dirty"), folio);
}

if (folio_mark_dirty(folio))
_debug("dirtied %lx", folio_index(folio));

out:
folio_unlock(folio);
folio_put(folio);
return copied;
}

/*
* kill all the pages in the given range
*/
@@ -825,38 +681,6 @@ int afs_writepages(struct address_space *mapping,
return ret;
}

/*
* write to an AFS file
*/
ssize_t afs_file_write(struct kiocb *iocb, struct iov_iter *from)
{
struct afs_vnode *vnode = AFS_FS_I(file_inode(iocb->ki_filp));
struct afs_file *af = iocb->ki_filp->private_data;
ssize_t result;
size_t count = iov_iter_count(from);

_enter("{%llx:%llu},{%zu},",
vnode->fid.vid, vnode->fid.vnode, count);

if (IS_SWAPFILE(&vnode->vfs_inode)) {
printk(KERN_INFO
"AFS: Attempt to write to active swap file!\n");
return -EBUSY;
}

if (!count)
return 0;

result = afs_validate(vnode, af->key);
if (result < 0)
return result;

result = generic_file_write_iter(iocb, from);

_leave(" = %zd", result);
return result;
}

/*
* flush any dirty pages for this process, and check for write errors.
* - the return status from this call provides a reliable indication of
@@ -2,9 +2,11 @@

netfs-y := \
direct.o \
flush.o \
misc.o \
objects.o \
read_helper.o
read_helper.o \
write_helper.o

netfs-$(CONFIG_NETFS_STATS) += stats.o

0 comments on commit 01abe2e

Please sign in to comment.