From 3788d67101ca692a2606d4a30cefab200a6c3971 Mon Sep 17 00:00:00 2001 From: Benjamin LaHaise Date: Mon, 27 Jul 2020 17:04:22 -0400 Subject: [PATCH] Add support for writable shared mmap()ings Add support for writable MAP_SHARED mmap()ings. Avoid issues with late writepage()s building transactions by doing the block_write_begin() work in scoutfs_data_page_mkwrite(). Ensure the page is marked dirty and prepared for write, then let the VM complete the write when the page is flushed or invalidated. Signed-off-by: Benjamin LaHaise Signed-off-by: Auke Kok --- kmod/src/data.c | 153 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 3 deletions(-) diff --git a/kmod/src/data.c b/kmod/src/data.c index 63ed830d..ce3cafe8 100644 --- a/kmod/src/data.c +++ b/kmod/src/data.c @@ -560,7 +560,7 @@ static int scoutfs_get_block(struct inode *inode, sector_t iblock, u64 offset; int ret; - WARN_ON_ONCE(create && !inode_is_locked(inode)); + WARN_ON_ONCE(create && !rwsem_is_locked(&si->extent_sem)); /* make sure caller holds a cluster lock */ lock = scoutfs_per_task_get(&si->pt_data_lock); @@ -1914,6 +1914,154 @@ int scoutfs_data_waiting(struct super_block *sb, u64 ino, u64 iblock, return ret; } +#ifdef KC_MM_VM_FAULT_T +static vm_fault_t scoutfs_data_page_mkwrite(struct vm_fault *vmf) +{ + struct vm_area_struct *vma = vmf->vma; +#else +static int scoutfs_data_page_mkwrite(struct vm_area_struct *vma, + struct vm_fault *vmf) +{ +#endif + struct page *page = vmf->page; + struct file *file = vma->vm_file; + struct inode *inode = file_inode(file); + struct scoutfs_inode_info *si = SCOUTFS_I(inode); + struct super_block *sb = inode->i_sb; + struct scoutfs_lock *lock = NULL; + SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_ent); + DECLARE_DATA_WAIT(dw); + struct write_begin_data wbd; + u64 ind_seq; + loff_t pos; + loff_t size; + unsigned int len = PAGE_SIZE; + vm_fault_t ret = VM_FAULT_SIGBUS; + int err; + + pos = vmf->pgoff << PAGE_SHIFT; + + sb_start_pagefault(sb); + + err = scoutfs_lock_inode(sb, SCOUTFS_LOCK_WRITE, + SCOUTFS_LKF_REFRESH_INODE, inode, &lock); + if (err) { + ret = vmf_error(err); + goto out; + } + + size = i_size_read(inode); + + if (scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, lock)) { + /* data_version is per inode, whole file must be online */ + err = scoutfs_data_wait_check(inode, 0, size, + SEF_OFFLINE, + SCOUTFS_IOC_DWO_WRITE, + &dw, lock); + if (err != 0) { + if (err < 0) + ret = vmf_error(err); + goto out_unlock; + } + } + + + /* scoutfs_write_begin */ + memset(&wbd, 0, sizeof(wbd)); + INIT_LIST_HEAD(&wbd.ind_locks); + wbd.lock = lock; + + /* + * Start transaction before taking page locks - we want to make sure we're + * not locking a page, then waiting for trans, because writeback might race + * against it and cause a lock inversion hang - as demonstrated by both + * holetest and fsstress tests in xfstests. + */ + do { + err = scoutfs_inode_index_start(sb, &ind_seq) ?: + scoutfs_inode_index_prepare(sb, &wbd.ind_locks, inode, + true) ?: + scoutfs_inode_index_try_lock_hold(sb, &wbd.ind_locks, + ind_seq, false); + } while (err > 0); + if (err < 0) { + ret = vmf_error(err); + goto out_trans; + } + + down_write(&si->extent_sem); + + if (!trylock_page(page)) { + ret = VM_FAULT_NOPAGE; + goto out_sem; + } + ret = VM_FAULT_LOCKED; + + if ((page->mapping != inode->i_mapping) || + (!PageUptodate(page)) || + (page_offset(page) > size)) { + unlock_page(page); + ret = VM_FAULT_NOPAGE; + goto out_sem; + } + + if (page->index == (size - 1) >> PAGE_SHIFT) + len = ((size - 1) & ~PAGE_MASK) + 1; + + err = __block_write_begin(page, pos, PAGE_SIZE, scoutfs_get_block); + if (err) { + ret = vmf_error(err); + unlock_page(page); + goto out_sem; + } + /* end scoutfs_write_begin */ + + /* + * We mark the page dirty already here so that when freeze is in + * progress, we are guaranteed that writeback during freezing will + * see the dirty page and writeprotect it again. + */ + set_page_dirty(page); + wait_for_stable_page(page); + + /* scoutfs_write_end */ + scoutfs_inode_set_data_seq(inode); + scoutfs_inode_inc_data_version(inode); + + file_update_time(vma->vm_file); + + scoutfs_update_inode_item(inode, wbd.lock, &wbd.ind_locks); + scoutfs_inode_queue_writeback(inode); + +out_sem: + up_write(&si->extent_sem); +out_trans: + scoutfs_release_trans(sb); + scoutfs_inode_index_unlock(sb, &wbd.ind_locks); + /* end scoutfs_write_end */ + +out_unlock: + scoutfs_per_task_del(&si->pt_data_lock, &pt_ent); + scoutfs_unlock(sb, lock, SCOUTFS_LOCK_WRITE); + +out: + sb_end_pagefault(sb); + + if (scoutfs_data_wait_found(&dw)) { + /* + * It'd be really nice to not hold the mmap_sem lock here + * before waiting for data, and then return VM_FAULT_RETRY + */ + err = scoutfs_data_wait(inode, &dw); + if (err == 0) + ret = VM_FAULT_NOPAGE; + else + ret = vmf_error(err); + } + + return ret; +} + #ifdef KC_MM_VM_FAULT_T static vm_fault_t scoutfs_data_filemap_fault(struct vm_fault *vmf) { @@ -1979,13 +2127,12 @@ out: static const struct vm_operations_struct scoutfs_data_file_vm_ops = { .fault = scoutfs_data_filemap_fault, + .page_mkwrite = scoutfs_data_page_mkwrite, .remap_pages = generic_file_remap_pages, }; static int scoutfs_file_mmap(struct file *file, struct vm_area_struct *vma) { - if ((vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE)) - return -EINVAL; file_accessed(file); vma->vm_ops = &scoutfs_data_file_vm_ops; return 0;