From: Jeff Layton <jlayton@redhat.com> v8: === - rename filemap_report_wb_err to file_check_and_advance_wb_err (per Darrick's suggestion) - add a file_write_and_wait helper function (per HCH's suggestion) - change __generic_file_fsync to use errseq_t reporting - focus on data writeback for now, leave metadata for a follow-on set - drop patch to always do metadata writeback in __generic_file_fsync This is the eighth posting of the patchset to revamp the way writeback errors are tracked and reported. Some of these patches are not new, but I want to make it clear what I'd like to get merged into v4.13. The main changes since the last set are a name change for the "report" function to better indicate that it has side-effects. Also, I've added a file_write_and_wait function to act as an analogue of the old filemap_write_and_wait, but that uses errseq_t reporting. With that change, it became pretty easy to convert __generic_file_fsync, so I've gone ahead and done that. vfat now passes the simple testcase as well. Finally, I've dropped off a few patches here that were intended to make it possible to report metadata writeback errors on all open fds as well. That part is turning out to be trickier than expected, so I've decided to just focus on data writeback here. We can pick up that piece in a later patcheset. If no one has major objections, I'll plan to send Linus a PR with this pile once the merge window opens (unless someone else wants to pick it up...Al?). Background: =========== The basic problem is that we have (for a very long time) tracked and reported writeback errors based on two flags in the address_space: AS_EIO and AS_ENOSPC. Those flags are cleared when they are checked, so only the first caller to check them is able to consume them. That model is quite unreliable, for several related reasons: * only the first fsync caller on the inode will see the error. In a world of containerized setups, that's no longer viable. Applications need to know that their writes are safely stored, and they can currently miss seeing errors that they should be aware of when they're not. * there are a lot of internal callers to filemap_fdatawait* and filemap_write_and_wait* that clear these errors but then never report them to userland in any fashion. * Some internal callers report writeback errors, but can do so at non-sensical times. For instance, we might want to truncate a file, which triggers a pagecache flush. If that writeback fails, we might report that error to the truncate caller, but a subsequent fsync will likely not see it. * Some internal callers try to reset the error flags after clearing them, but that's racy. Another task could check the flags between those two events. Solution: ========= This patchset adds a new datatype called an errseq_t that represents a sequence of errors. It's a u32, with a field for a POSIX-flavor error and a counter, managed with atomics. We can sample that value at a particular point in time, and can later tell whether there have been any errors since that point. That allows us to provide traditional check-and-clear fsync semantics on every open file description in a lightweight fashion. fsync callers no longer need to coordinate between one another in order to ensure that errors at fsync time are handled correctly. Strategy: ========= The aim with this pile is to do the minimum possible to support for reliable reporting of errors on fsync, without substantially changing the internals of the filesystems themselves. Most of the internal calls to filemap_fdatawait are left alone, so all of the internal error checkers are using the same error handling they always have. The only real difference here is that we're better reporting errors at fsync. I think that we probably will want to eventually convert all of those internal callers to use errseq_t based reporting, but that can be done in an incremental fashion in follow-on patchsets. Testing: ======== I've primarily been testing this with some new xfstests that I will post in a separate series. These tests use dm-error fault injection to make the underlying block device start throwing I/O errors, and then test the how the filesystem layer reports errors after that. Jeff Layton (18): fs: remove call_fsync helper function buffer: use mapping_set_error instead of setting the flag fs: check for writeback errors after syncing out buffers in generic_file_fsync buffer: set errors in mapping at the time that the error occurs jbd2: don't clear and reset errors after waiting on writeback mm: clear AS_EIO/AS_ENOSPC when writeback initiation fails mm: don't TestClearPageError in __filemap_fdatawait_range mm: clean up error handling in write_one_page lib: add errseq_t type and infrastructure for handling it fs: new infrastructure for writeback error handling and reporting mm: set both AS_EIO/AS_ENOSPC and errseq_t in mapping_set_error Documentation: flesh out the section in vfs.txt on storing and reporting writeback errors dax: set errors in mapping when writeback fails block: convert to errseq_t based writeback error tracking fs: convert __generic_file_fsync to use errseq_t based reporting ext4: use errseq_t based error handling for reporting data writeback errors xfs: minimal conversion to errseq_t writeback error reporting btrfs: minimal conversion to errseq_t writeback error reporting on fsync Documentation/filesystems/vfs.txt | 43 +++++++- MAINTAINERS | 6 ++ drivers/dax/device.c | 1 + fs/block_dev.c | 3 +- fs/btrfs/file.c | 7 +- fs/buffer.c | 20 ++-- fs/dax.c | 4 +- fs/ext2/file.c | 5 +- fs/ext4/fsync.c | 13 ++- fs/file_table.c | 1 + fs/gfs2/lops.c | 2 +- fs/jbd2/commit.c | 16 +-- fs/libfs.c | 6 +- fs/open.c | 3 + fs/sync.c | 2 +- fs/xfs/xfs_file.c | 2 +- include/linux/buffer_head.h | 1 + include/linux/errseq.h | 19 ++++ include/linux/fs.h | 68 +++++++++++-- include/linux/pagemap.h | 31 ++++-- include/trace/events/filemap.h | 57 +++++++++++ ipc/shm.c | 2 +- lib/Makefile | 2 +- lib/errseq.c | 208 ++++++++++++++++++++++++++++++++++++++ mm/filemap.c | 128 +++++++++++++++++++---- mm/page-writeback.c | 13 ++- 26 files changed, 584 insertions(+), 79 deletions(-) create mode 100644 include/linux/errseq.h create mode 100644 lib/errseq.c -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> Reviewed-by: Christoph Hellwig <hch@lst.de> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com> Signed-off-by: Jeff Layton <jlayton@redhat.com> --- fs/sync.c | 2 +- include/linux/fs.h | 6 ------ ipc/shm.c | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/fs/sync.c b/fs/sync.c index 11ba023434b1..2a54c1f22035 100644 --- a/fs/sync.c +++ b/fs/sync.c @@ -192,7 +192,7 @@ int vfs_fsync_range(struct file *file, loff_t start, loff_t end, int datasync) spin_unlock(&inode->i_lock); mark_inode_dirty_sync(inode); } - return call_fsync(file, start, end, datasync); + return file->f_op->fsync(file, start, end, datasync); } EXPORT_SYMBOL(vfs_fsync_range); diff --git a/include/linux/fs.h b/include/linux/fs.h index 1e53cd281437..4ff4498297fa 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1739,12 +1739,6 @@ static inline int call_mmap(struct file *file, struct vm_area_struct *vma) return file->f_op->mmap(file, vma); } -static inline int call_fsync(struct file *file, loff_t start, loff_t end, - int datasync) -{ - return file->f_op->fsync(file, start, end, datasync); -} - ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector, unsigned long nr_segs, unsigned long fast_segs, struct iovec *fast_pointer, diff --git a/ipc/shm.c b/ipc/shm.c index 34c4344e8d4b..f45c7959b264 100644 --- a/ipc/shm.c +++ b/ipc/shm.c @@ -452,7 +452,7 @@ static int shm_fsync(struct file *file, loff_t start, loff_t end, int datasync) if (!sfd->file->f_op->fsync) return -EINVAL; - return call_fsync(sfd->file, start, end, datasync); + return sfd->file->f_op->fsync(sfd->file, start, end, datasync); } static long shm_fallocate(struct file *file, int mode, loff_t offset, -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> Signed-off-by: Jeff Layton <jlayton@redhat.com> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Matthew Wilcox <mawilcox@microsoft.com> Reviewed-by: Christoph Hellwig <hch@lst.de> --- fs/buffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/buffer.c b/fs/buffer.c index 161be58c5cb0..4be8b914a222 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -482,7 +482,7 @@ static void __remove_assoc_queue(struct buffer_head *bh) list_del_init(&bh->b_assoc_buffers); WARN_ON(!bh->b_assoc_map); if (buffer_write_io_error(bh)) - set_bit(AS_EIO, &bh->b_assoc_map->flags); + mapping_set_error(bh->b_assoc_map, -EIO); bh->b_assoc_map = NULL; } -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> ext2 currently does a test+clear of the AS_EIO flag, which is is problematic for some coming changes. What we really need to do instead is call filemap_check_errors in __generic_file_fsync after syncing out the buffers. That will be sufficient for this case, and help other callers detect these errors properly as well. With that, we don't need to twiddle it in ext2. Suggested-by: Jan Kara <jack@suse.cz> Signed-off-by: Jeff Layton <jlayton@redhat.com> Reviewed-by: Christoph Hellwig <hch@lst.de> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Matthew Wilcox <mawilcox@microsoft.com> --- fs/ext2/file.c | 5 +---- fs/libfs.c | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/fs/ext2/file.c b/fs/ext2/file.c index b21891a6bfca..d34d32bdc944 100644 --- a/fs/ext2/file.c +++ b/fs/ext2/file.c @@ -174,15 +174,12 @@ int ext2_fsync(struct file *file, loff_t start, loff_t end, int datasync) { int ret; struct super_block *sb = file->f_mapping->host->i_sb; - struct address_space *mapping = sb->s_bdev->bd_inode->i_mapping; ret = generic_file_fsync(file, start, end, datasync); - if (ret == -EIO || test_and_clear_bit(AS_EIO, &mapping->flags)) { + if (ret == -EIO) /* We don't really know where the IO error happened... */ ext2_error(sb, __func__, "detected IO error when writing metadata buffers"); - ret = -EIO; - } return ret; } diff --git a/fs/libfs.c b/fs/libfs.c index a04395334bb1..1dec90819366 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -991,7 +991,8 @@ int __generic_file_fsync(struct file *file, loff_t start, loff_t end, out: inode_unlock(inode); - return ret; + err = filemap_check_errors(inode->i_mapping); + return ret ? ret : err; } EXPORT_SYMBOL(__generic_file_fsync); -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> I noticed on xfs that I could still sometimes get back an error on fsync on a fd that was opened after the error condition had been cleared. The problem is that the buffer code sets the write_io_error flag and then later checks that flag to set the error in the mapping. That flag perisists for quite a while however. If the file is later opened with O_TRUNC, the buffers will then be invalidated and the mapping's error set such that a subsequent fsync will return error. I think this is incorrect, as there was no writeback between the open and fsync. Add a new mark_buffer_write_io_error operation that sets the flag and the error in the mapping at the same time. Replace all calls to set_buffer_write_io_error with mark_buffer_write_io_error, and remove the places that check this flag in order to set the error in the mapping. This sets the error in the mapping earlier, at the time that it's first detected. Signed-off-by: Jeff Layton <jlayton@redhat.com> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com> --- fs/buffer.c | 20 +++++++++++++------- fs/gfs2/lops.c | 2 +- include/linux/buffer_head.h | 1 + 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/fs/buffer.c b/fs/buffer.c index 4be8b914a222..b946149e8214 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -178,7 +178,7 @@ void end_buffer_write_sync(struct buffer_head *bh, int uptodate) set_buffer_uptodate(bh); } else { buffer_io_error(bh, ", lost sync page write"); - set_buffer_write_io_error(bh); + mark_buffer_write_io_error(bh); clear_buffer_uptodate(bh); } unlock_buffer(bh); @@ -352,8 +352,7 @@ void end_buffer_async_write(struct buffer_head *bh, int uptodate) set_buffer_uptodate(bh); } else { buffer_io_error(bh, ", lost async page write"); - mapping_set_error(page->mapping, -EIO); - set_buffer_write_io_error(bh); + mark_buffer_write_io_error(bh); clear_buffer_uptodate(bh); SetPageError(page); } @@ -481,8 +480,6 @@ static void __remove_assoc_queue(struct buffer_head *bh) { list_del_init(&bh->b_assoc_buffers); WARN_ON(!bh->b_assoc_map); - if (buffer_write_io_error(bh)) - mapping_set_error(bh->b_assoc_map, -EIO); bh->b_assoc_map = NULL; } @@ -1181,6 +1178,17 @@ void mark_buffer_dirty(struct buffer_head *bh) } EXPORT_SYMBOL(mark_buffer_dirty); +void mark_buffer_write_io_error(struct buffer_head *bh) +{ + set_buffer_write_io_error(bh); + /* FIXME: do we need to set this in both places? */ + if (bh->b_page && bh->b_page->mapping) + mapping_set_error(bh->b_page->mapping, -EIO); + if (bh->b_assoc_map) + mapping_set_error(bh->b_assoc_map, -EIO); +} +EXPORT_SYMBOL(mark_buffer_write_io_error); + /* * Decrement a buffer_head's reference count. If all buffers against a page * have zero reference count, are clean and unlocked, and if the page is clean @@ -3279,8 +3287,6 @@ drop_buffers(struct page *page, struct buffer_head **buffers_to_free) bh = head; do { - if (buffer_write_io_error(bh) && page->mapping) - mapping_set_error(page->mapping, -EIO); if (buffer_busy(bh)) goto failed; bh = bh->b_this_page; diff --git a/fs/gfs2/lops.c b/fs/gfs2/lops.c index b1f9144b42c7..cd7857ab1a6a 100644 --- a/fs/gfs2/lops.c +++ b/fs/gfs2/lops.c @@ -182,7 +182,7 @@ static void gfs2_end_log_write_bh(struct gfs2_sbd *sdp, struct bio_vec *bvec, bh = bh->b_this_page; do { if (error) - set_buffer_write_io_error(bh); + mark_buffer_write_io_error(bh); unlock_buffer(bh); next = bh->b_this_page; size -= bh->b_size; diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index bd029e52ef5e..e0abeba3ced7 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -149,6 +149,7 @@ void buffer_check_dirty_writeback(struct page *page, */ void mark_buffer_dirty(struct buffer_head *bh); +void mark_buffer_write_io_error(struct buffer_head *bh); void init_buffer(struct buffer_head *, bh_end_io_t *, void *); void touch_buffer(struct buffer_head *bh); void set_bh_page(struct buffer_head *bh, -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> Resetting this flag is almost certainly racy, and will be problematic with some coming changes. Make filemap_fdatawait_keep_errors return int, but not clear the flag(s). Have jbd2 call it instead of filemap_fdatawait and don't attempt to re-set the error flag if it fails. Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com> Signed-off-by: Jeff Layton <jlayton@redhat.com> --- fs/jbd2/commit.c | 16 ++++------------ include/linux/fs.h | 2 +- mm/filemap.c | 16 ++++++++++++++-- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/fs/jbd2/commit.c b/fs/jbd2/commit.c index b6b194ec1b4f..3c1c31321d9b 100644 --- a/fs/jbd2/commit.c +++ b/fs/jbd2/commit.c @@ -263,18 +263,10 @@ static int journal_finish_inode_data_buffers(journal_t *journal, continue; jinode->i_flags |= JI_COMMIT_RUNNING; spin_unlock(&journal->j_list_lock); - err = filemap_fdatawait(jinode->i_vfs_inode->i_mapping); - if (err) { - /* - * Because AS_EIO is cleared by - * filemap_fdatawait_range(), set it again so - * that user process can get -EIO from fsync(). - */ - mapping_set_error(jinode->i_vfs_inode->i_mapping, -EIO); - - if (!ret) - ret = err; - } + err = filemap_fdatawait_keep_errors( + jinode->i_vfs_inode->i_mapping); + if (!ret) + ret = err; spin_lock(&journal->j_list_lock); jinode->i_flags &= ~JI_COMMIT_RUNNING; smp_mb(); diff --git a/include/linux/fs.h b/include/linux/fs.h index 4ff4498297fa..74872c0f1c07 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2508,7 +2508,7 @@ extern int write_inode_now(struct inode *, int); extern int filemap_fdatawrite(struct address_space *); extern int filemap_flush(struct address_space *); extern int filemap_fdatawait(struct address_space *); -extern void filemap_fdatawait_keep_errors(struct address_space *); +extern int filemap_fdatawait_keep_errors(struct address_space *mapping); extern int filemap_fdatawait_range(struct address_space *, loff_t lstart, loff_t lend); extern int filemap_write_and_wait(struct address_space *mapping); diff --git a/mm/filemap.c b/mm/filemap.c index 6f1be573a5e6..e5711b2728f4 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -309,6 +309,16 @@ int filemap_check_errors(struct address_space *mapping) } EXPORT_SYMBOL(filemap_check_errors); +static int filemap_check_and_keep_errors(struct address_space *mapping) +{ + /* Check for outstanding write errors */ + if (test_bit(AS_EIO, &mapping->flags)) + return -EIO; + if (test_bit(AS_ENOSPC, &mapping->flags)) + return -ENOSPC; + return 0; +} + /** * __filemap_fdatawrite_range - start writeback on mapping dirty pages in range * @mapping: address space structure to write @@ -453,15 +463,17 @@ EXPORT_SYMBOL(filemap_fdatawait_range); * call sites are system-wide / filesystem-wide data flushers: e.g. sync(2), * fsfreeze(8) */ -void filemap_fdatawait_keep_errors(struct address_space *mapping) +int filemap_fdatawait_keep_errors(struct address_space *mapping) { loff_t i_size = i_size_read(mapping->host); if (i_size == 0) - return; + return 0; __filemap_fdatawait_range(mapping, 0, i_size - 1); + return filemap_check_and_keep_errors(mapping); } +EXPORT_SYMBOL(filemap_fdatawait_keep_errors); /** * filemap_fdatawait - wait for all under-writeback pages to complete -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> filemap_write_and_wait{_range} will return an error if writeback initiation fails, but won't clear errors in the address_space. This is particularly problematic on DAX, as it's effectively synchronous. Ensure that we clear the AS_EIO/AS_ENOSPC flags when filemap_fdatawrite returns an error. Signed-off-by: Jeff Layton <jlayton@redhat.com> --- mm/filemap.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mm/filemap.c b/mm/filemap.c index e5711b2728f4..49bc9720fb00 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -515,6 +515,9 @@ int filemap_write_and_wait(struct address_space *mapping) int err2 = filemap_fdatawait(mapping); if (!err) err = err2; + } else { + /* Clear any previously stored errors */ + filemap_check_errors(mapping); } } else { err = filemap_check_errors(mapping); @@ -549,6 +552,9 @@ int filemap_write_and_wait_range(struct address_space *mapping, lstart, lend); if (!err) err = err2; + } else { + /* Clear any previously stored errors */ + filemap_check_errors(mapping); } } else { err = filemap_check_errors(mapping); -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> The -EIO returned here can end up overriding whatever error is marked in the address space, and be returned at fsync time, even when there is a more appropriate error stored in the mapping. Read errors are also sometimes tracked on a per-page level using PG_error. Suppose we have a read error on a page, and then that page is subsequently dirtied by overwriting the whole page. Writeback doesn't clear PG_error, so we can then end up successfully writing back that page and still return -EIO on fsync. Worse yet, PG_error is cleared during a sync() syscall, but the -EIO return from that is silently discarded. Any subsystem that is relying on PG_error to report errors during fsync can easily lose writeback errors due to this. All you need is a stray sync() call to wait for writeback to complete and you've lost the error. Since the handling of the PG_error flag is somewhat inconsistent across subsystems, let's just rely on marking the address space when there are writeback errors. Change the TestClearPageError call to ClearPageError, and make __filemap_fdatawait_range a void return function. Signed-off-by: Jeff Layton <jlayton@redhat.com> --- mm/filemap.c | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/mm/filemap.c b/mm/filemap.c index 49bc9720fb00..eb99b5f23c61 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -386,17 +386,16 @@ int filemap_flush(struct address_space *mapping) } EXPORT_SYMBOL(filemap_flush); -static int __filemap_fdatawait_range(struct address_space *mapping, +static void __filemap_fdatawait_range(struct address_space *mapping, loff_t start_byte, loff_t end_byte) { pgoff_t index = start_byte >> PAGE_SHIFT; pgoff_t end = end_byte >> PAGE_SHIFT; struct pagevec pvec; int nr_pages; - int ret = 0; if (end_byte < start_byte) - goto out; + return; pagevec_init(&pvec, 0); while ((index <= end) && @@ -413,14 +412,11 @@ static int __filemap_fdatawait_range(struct address_space *mapping, continue; wait_on_page_writeback(page); - if (TestClearPageError(page)) - ret = -EIO; + ClearPageError(page); } pagevec_release(&pvec); cond_resched(); } -out: - return ret; } /** @@ -440,14 +436,8 @@ static int __filemap_fdatawait_range(struct address_space *mapping, int filemap_fdatawait_range(struct address_space *mapping, loff_t start_byte, loff_t end_byte) { - int ret, ret2; - - ret = __filemap_fdatawait_range(mapping, start_byte, end_byte); - ret2 = filemap_check_errors(mapping); - if (!ret) - ret = ret2; - - return ret; + __filemap_fdatawait_range(mapping, start_byte, end_byte); + return filemap_check_errors(mapping); } EXPORT_SYMBOL(filemap_fdatawait_range); -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> Don't try to check PageError since that's potentially racy and not necessarily going to be set after writepage errors out. Instead, check the mapping for an error after writepage returns. Signed-off-by: Jeff Layton <jlayton@redhat.com> Reviewed-by: Jan Kara <jack@suse.cz> --- mm/page-writeback.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/mm/page-writeback.c b/mm/page-writeback.c index b901fe52b153..db30ce0b7d80 100644 --- a/mm/page-writeback.c +++ b/mm/page-writeback.c @@ -2371,9 +2371,8 @@ int do_writepages(struct address_space *mapping, struct writeback_control *wbc) * * The page must be locked by the caller and will be unlocked upon return. * - * write_one_page() returns a negative error code if I/O failed. Note that - * the address_space is not marked for error. The caller must do this if - * needed. + * Note that the mapping's AS_EIO/AS_ENOSPC flags will be cleared when this + * function returns. */ int write_one_page(struct page *page) { @@ -2391,15 +2390,15 @@ int write_one_page(struct page *page) if (clear_page_dirty_for_io(page)) { get_page(page); ret = mapping->a_ops->writepage(page, &wbc); - if (ret == 0) { + if (ret == 0) wait_on_page_writeback(page); - if (PageError(page)) - ret = -EIO; - } put_page(page); } else { unlock_page(page); } + + if (!ret) + ret = filemap_check_errors(mapping); return ret; } EXPORT_SYMBOL(write_one_page); -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> An errseq_t is a way of recording errors in one place, and allowing any number of "subscribers" to tell whether an error has been set again since a previous time. It's implemented as an unsigned 32-bit value that is managed with atomic operations. The low order bits are designated to hold an error code (max size of MAX_ERRNO). The upper bits are used as a counter. The API works with consumers sampling an errseq_t value at a particular point in time. Later, that value can be used to tell whether new errors have been set since that time. Note that there is a 1 in 512k risk of collisions here if new errors are being recorded frequently, since we have so few bits to use as a counter. To mitigate this, one bit is used as a flag to tell whether the value has been sampled since a new value was recorded. That allows us to avoid bumping the counter if no one has sampled it since it was last bumped. Later patches will build on this infrastructure to change how writeback errors are tracked in the kernel. Signed-off-by: Jeff Layton <jlayton@redhat.com> Reviewed-by: NeilBrown <neilb@suse.com> Reviewed-by: Jan Kara <jack@suse.cz> --- MAINTAINERS | 6 ++ include/linux/errseq.h | 19 +++++ lib/Makefile | 2 +- lib/errseq.c | 208 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 include/linux/errseq.h create mode 100644 lib/errseq.c diff --git a/MAINTAINERS b/MAINTAINERS index 9e984645c4b0..c2465dc21946 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4999,6 +4999,12 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/kristoffer/linux-hpc.git F: drivers/video/fbdev/s1d13xxxfb.c F: include/video/s1d13xxxfb.h +ERRSEQ ERROR TRACKING INFRASTRUCTURE +M: Jeff Layton <jlayton@poochiereds.net> +S: Maintained +F: lib/errseq.c +F: include/linux/errseq.h + ET131X NETWORK DRIVER M: Mark Einon <mark.einon@gmail.com> S: Odd Fixes diff --git a/include/linux/errseq.h b/include/linux/errseq.h new file mode 100644 index 000000000000..9e0d444ac88d --- /dev/null +++ b/include/linux/errseq.h @@ -0,0 +1,19 @@ +#ifndef _LINUX_ERRSEQ_H +#define _LINUX_ERRSEQ_H + +/* See lib/errseq.c for more info */ + +typedef u32 errseq_t; + +errseq_t __errseq_set(errseq_t *eseq, int err); +static inline void errseq_set(errseq_t *eseq, int err) +{ + /* Optimize for the common case of no error */ + if (unlikely(err)) + __errseq_set(eseq, err); +} + +errseq_t errseq_sample(errseq_t *eseq); +int errseq_check(errseq_t *eseq, errseq_t since); +int errseq_check_and_advance(errseq_t *eseq, errseq_t *since); +#endif diff --git a/lib/Makefile b/lib/Makefile index 0166fbc0fa81..519782d9ca3f 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -41,7 +41,7 @@ obj-y += bcd.o div64.o sort.o parser.o debug_locks.o random32.o \ gcd.o lcm.o list_sort.o uuid.o flex_array.o iov_iter.o clz_ctz.o \ bsearch.o find_bit.o llist.o memweight.o kfifo.o \ percpu-refcount.o percpu_ida.o rhashtable.o reciprocal_div.o \ - once.o refcount.o usercopy.o + once.o refcount.o usercopy.o errseq.o obj-y += string_helpers.o obj-$(CONFIG_TEST_STRING_HELPERS) += test-string_helpers.o obj-y += hexdump.o diff --git a/lib/errseq.c b/lib/errseq.c new file mode 100644 index 000000000000..841fa24e6e00 --- /dev/null +++ b/lib/errseq.c @@ -0,0 +1,208 @@ +#include <linux/err.h> +#include <linux/bug.h> +#include <linux/atomic.h> +#include <linux/errseq.h> + +/* + * An errseq_t is a way of recording errors in one place, and allowing any + * number of "subscribers" to tell whether it has changed since a previous + * point where it was sampled. + * + * It's implemented as an unsigned 32-bit value. The low order bits are + * designated to hold an error code (between 0 and -MAX_ERRNO). The upper bits + * are used as a counter. This is done with atomics instead of locking so that + * these functions can be called from any context. + * + * The general idea is for consumers to sample an errseq_t value. That value + * can later be used to tell whether any new errors have occurred since that + * sampling was done. + * + * Note that there is a risk of collisions if new errors are being recorded + * frequently, since we have so few bits to use as a counter. + * + * To mitigate this, one bit is used as a flag to tell whether the value has + * been sampled since a new value was recorded. That allows us to avoid bumping + * the counter if no one has sampled it since the last time an error was + * recorded. + * + * A new errseq_t should always be zeroed out. A errseq_t value of all zeroes + * is the special (but common) case where there has never been an error. An all + * zero value thus serves as the "epoch" if one wishes to know whether there + * has ever been an error set since it was first initialized. + */ + +/* The low bits are designated for error code (max of MAX_ERRNO) */ +#define ERRSEQ_SHIFT ilog2(MAX_ERRNO + 1) + +/* This bit is used as a flag to indicate whether the value has been seen */ +#define ERRSEQ_SEEN (1 << ERRSEQ_SHIFT) + +/* The lowest bit of the counter */ +#define ERRSEQ_CTR_INC (1 << (ERRSEQ_SHIFT + 1)) + +/** + * __errseq_set - set a errseq_t for later reporting + * @eseq: errseq_t field that should be set + * @err: error to set + * + * This function sets the error in *eseq, and increments the sequence counter + * if the last sequence was sampled at some point in the past. + * + * Any error set will always overwrite an existing error. + * + * Most callers will want to use the errseq_set inline wrapper to efficiently + * handle the common case where err is 0. + * + * We do return an errseq_t here, primarily for debugging purposes. The return + * value should not be used as a previously sampled value in later calls as it + * will not have the SEEN flag set. + */ +errseq_t __errseq_set(errseq_t *eseq, int err) +{ + errseq_t cur, old; + + /* MAX_ERRNO must be able to serve as a mask */ + BUILD_BUG_ON_NOT_POWER_OF_2(MAX_ERRNO + 1); + + /* + * Ensure the error code actually fits where we want it to go. If it + * doesn't then just throw a warning and don't record anything. We + * also don't accept zero here as that would effectively clear a + * previous error. + */ + old = READ_ONCE(*eseq); + + if (WARN(unlikely(err == 0 || (unsigned int)-err > MAX_ERRNO), + "err = %d\n", err)) + return old; + + for (;;) { + errseq_t new; + + /* Clear out error bits and set new error */ + new = (old & ~(MAX_ERRNO|ERRSEQ_SEEN)) | -err; + + /* Only increment if someone has looked at it */ + if (old & ERRSEQ_SEEN) + new += ERRSEQ_CTR_INC; + + /* If there would be no change, then call it done */ + if (new == old) { + cur = new; + break; + } + + /* Try to swap the new value into place */ + cur = cmpxchg(eseq, old, new); + + /* + * Call it success if we did the swap or someone else beat us + * to it for the same value. + */ + if (likely(cur == old || cur == new)) + break; + + /* Raced with an update, try again */ + old = cur; + } + return cur; +} +EXPORT_SYMBOL(__errseq_set); + +/** + * errseq_sample - grab current errseq_t value + * @eseq: pointer to errseq_t to be sampled + * + * This function allows callers to sample an errseq_t value, marking it as + * "seen" if required. + */ +errseq_t errseq_sample(errseq_t *eseq) +{ + errseq_t old = READ_ONCE(*eseq); + errseq_t new = old; + + /* + * For the common case of no errors ever having been set, we can skip + * marking the SEEN bit. Once an error has been set, the value will + * never go back to zero. + */ + if (old != 0) { + new |= ERRSEQ_SEEN; + if (old != new) + cmpxchg(eseq, old, new); + } + return new; +} +EXPORT_SYMBOL(errseq_sample); + +/** + * errseq_check - has an error occurred since a particular sample point? + * @eseq: pointer to errseq_t value to be checked + * @since: previously-sampled errseq_t from which to check + * + * Grab the value that eseq points to, and see if it has changed "since" + * the given value was sampled. The "since" value is not advanced, so there + * is no need to mark the value as seen. + * + * Returns the latest error set in the errseq_t or 0 if it hasn't changed. + */ +int errseq_check(errseq_t *eseq, errseq_t since) +{ + errseq_t cur = READ_ONCE(*eseq); + + if (likely(cur == since)) + return 0; + return -(cur & MAX_ERRNO); +} +EXPORT_SYMBOL(errseq_check); + +/** + * errseq_check_and_advance - check an errseq_t and advance to current value + * @eseq: pointer to value being checked and reported + * @since: pointer to previously-sampled errseq_t to check against and advance + * + * Grab the eseq value, and see whether it matches the value that "since" + * points to. If it does, then just return 0. + * + * If it doesn't, then the value has changed. Set the "seen" flag, and try to + * swap it into place as the new eseq value. Then, set that value as the new + * "since" value, and return whatever the error portion is set to. + * + * Note that no locking is provided here for concurrent updates to the "since" + * value. The caller must provide that if necessary. Because of this, callers + * may want to do a lockless errseq_check before taking the lock and calling + * this. + */ +int errseq_check_and_advance(errseq_t *eseq, errseq_t *since) +{ + int err = 0; + errseq_t old, new; + + /* + * Most callers will want to use the inline wrapper to check this, + * so that the common case of no error is handled without needing + * to take the lock that protects the "since" value. + */ + old = READ_ONCE(*eseq); + if (old != *since) { + /* + * Set the flag and try to swap it into place if it has + * changed. + * + * We don't care about the outcome of the swap here. If the + * swap doesn't occur, then it has either been updated by a + * writer who is altering the value in some way (updating + * counter or resetting the error), or another reader who is + * just setting the "seen" flag. Either outcome is OK, and we + * can advance "since" and return an error based on what we + * have. + */ + new = old | ERRSEQ_SEEN; + if (new != old) + cmpxchg(eseq, old, new); + *since = new; + err = -(new & MAX_ERRNO); + } + return err; +} +EXPORT_SYMBOL(errseq_check_and_advance); -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> Most filesystems currently use mapping_set_error and filemap_check_errors for setting and reporting/clearing writeback errors at the mapping level. filemap_check_errors is indirectly called from most of the filemap_fdatawait_* functions and from filemap_write_and_wait*. These functions are called from all sorts of contexts to wait on writeback to finish -- e.g. mostly in fsync, but also in truncate calls, getattr, etc. The non-fsync callers are problematic. We should be reporting writeback errors during fsync, but many places spread over the tree clear out errors before they can be properly reported, or report errors at nonsensical times. If I get -EIO on a stat() call, there is no reason for me to assume that it is because some previous writeback failed. The fact that it also clears out the error such that a subsequent fsync returns 0 is a bug, and a nasty one since that's potentially silent data corruption. This patch adds a small bit of new infrastructure for setting and reporting errors during address_space writeback. While the above was my original impetus for adding this, I think it's also the case that current fsync semantics are just problematic for userland. Most applications that call fsync do so to ensure that the data they wrote has hit the backing store. In the case where there are multiple writers to the file at the same time, this is really hard to determine. The first one to call fsync will see any stored error, and the rest get back 0. The processes with open fds may not be associated with one another in any way. They could even be in different containers, so ensuring coordination between all fsync callers is not really an option. One way to remedy this would be to track what file descriptor was used to dirty the file, but that's rather cumbersome and would likely be slow. However, there is a simpler way to improve the semantics here without incurring too much overhead. This set adds an errseq_t to struct address_space, and a corresponding one is added to struct file. Writeback errors are recorded in the mapping's errseq_t, and the one in struct file is used as the "since" value. This changes the semantics of the Linux fsync implementation such that applications can now use it to determine whether there were any writeback errors since fsync(fd) was last called (or since the file was opened in the case of fsync having never been called). Note that those writeback errors may have occurred when writing data that was dirtied via an entirely different fd, but that's the case now with the current mapping_set_error/filemap_check_error infrastructure. This will at least prevent you from getting a false report of success. The new behavior is still consistent with the POSIX spec, and is more reliable for application developers. This patch just adds some basic infrastructure for doing this, and ensures that the f_wb_err "cursor" is properly set when a file is opened. Later patches will change the existing code to use this new infrastructure for reporting errors at fsync time. Signed-off-by: Jeff Layton <jlayton@redhat.com> Reviewed-by: Jan Kara <jack@suse.cz> --- drivers/dax/device.c | 1 + fs/block_dev.c | 1 + fs/file_table.c | 1 + fs/open.c | 3 ++ include/linux/fs.h | 60 ++++++++++++++++++++++++++++- include/trace/events/filemap.h | 57 ++++++++++++++++++++++++++++ mm/filemap.c | 86 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 208 insertions(+), 1 deletion(-) diff --git a/drivers/dax/device.c b/drivers/dax/device.c index 006e657dfcb9..12943d19bfc4 100644 --- a/drivers/dax/device.c +++ b/drivers/dax/device.c @@ -499,6 +499,7 @@ static int dax_open(struct inode *inode, struct file *filp) inode->i_mapping = __dax_inode->i_mapping; inode->i_mapping->host = __dax_inode; filp->f_mapping = inode->i_mapping; + filp->f_wb_err = filemap_sample_wb_err(filp->f_mapping); filp->private_data = dev_dax; inode->i_flags = S_DAX; diff --git a/fs/block_dev.c b/fs/block_dev.c index 519599dddd36..4d62fe771587 100644 --- a/fs/block_dev.c +++ b/fs/block_dev.c @@ -1743,6 +1743,7 @@ static int blkdev_open(struct inode * inode, struct file * filp) return -ENOMEM; filp->f_mapping = bdev->bd_inode->i_mapping; + filp->f_wb_err = filemap_sample_wb_err(filp->f_mapping); return blkdev_get(bdev, filp->f_mode, filp); } diff --git a/fs/file_table.c b/fs/file_table.c index 954d510b765a..72e861a35a7f 100644 --- a/fs/file_table.c +++ b/fs/file_table.c @@ -168,6 +168,7 @@ struct file *alloc_file(const struct path *path, fmode_t mode, file->f_path = *path; file->f_inode = path->dentry->d_inode; file->f_mapping = path->dentry->d_inode->i_mapping; + file->f_wb_err = filemap_sample_wb_err(file->f_mapping); if ((mode & FMODE_READ) && likely(fop->read || fop->read_iter)) mode |= FMODE_CAN_READ; diff --git a/fs/open.c b/fs/open.c index cd0c5be8d012..280d4a963791 100644 --- a/fs/open.c +++ b/fs/open.c @@ -707,6 +707,9 @@ static int do_dentry_open(struct file *f, f->f_inode = inode; f->f_mapping = inode->i_mapping; + /* Ensure that we skip any errors that predate opening of the file */ + f->f_wb_err = filemap_sample_wb_err(f->f_mapping); + if (unlikely(f->f_flags & O_PATH)) { f->f_mode = FMODE_PATH; f->f_op = &empty_fops; diff --git a/include/linux/fs.h b/include/linux/fs.h index 74872c0f1c07..b524fd442057 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -30,7 +30,7 @@ #include <linux/percpu-rwsem.h> #include <linux/workqueue.h> #include <linux/delayed_call.h> - +#include <linux/errseq.h> #include <asm/byteorder.h> #include <uapi/linux/fs.h> @@ -392,6 +392,7 @@ struct address_space { gfp_t gfp_mask; /* implicit gfp mask for allocations */ struct list_head private_list; /* ditto */ void *private_data; /* ditto */ + errseq_t wb_err; } __attribute__((aligned(sizeof(long)))); /* * On most architectures that alignment is already the case; but @@ -846,6 +847,7 @@ struct file { * Must not be taken from IRQ context. */ spinlock_t f_lock; + errseq_t f_wb_err; atomic_long_t f_count; unsigned int f_flags; fmode_t f_mode; @@ -2520,6 +2522,62 @@ extern int filemap_fdatawrite_range(struct address_space *mapping, loff_t start, loff_t end); extern int filemap_check_errors(struct address_space *mapping); +extern void __filemap_set_wb_err(struct address_space *mapping, int err); +extern int __must_check file_check_and_advance_wb_err(struct file *file); +extern int __must_check file_write_and_wait_range(struct file *file, + loff_t start, loff_t end); + +/** + * filemap_set_wb_err - set a writeback error on an address_space + * @mapping: mapping in which to set writeback error + * @err: error to be set in mapping + * + * When writeback fails in some way, we must record that error so that + * userspace can be informed when fsync and the like are called. We endeavor + * to report errors on any file that was open at the time of the error. Some + * internal callers also need to know when writeback errors have occurred. + * + * When a writeback error occurs, most filesystems will want to call + * filemap_set_wb_err to record the error in the mapping so that it will be + * automatically reported whenever fsync is called on the file. + * + * FIXME: mention FS_* flag here? + */ +static inline void filemap_set_wb_err(struct address_space *mapping, int err) +{ + /* Fastpath for common case of no error */ + if (unlikely(err)) + __filemap_set_wb_err(mapping, err); +} + +/** + * filemap_check_wb_error - has an error occurred since the mark was sampled? + * @mapping: mapping to check for writeback errors + * @since: previously-sampled errseq_t + * + * Grab the errseq_t value from the mapping, and see if it has changed "since" + * the given value was sampled. + * + * If it has then report the latest error set, otherwise return 0. + */ +static inline int filemap_check_wb_err(struct address_space *mapping, + errseq_t since) +{ + return errseq_check(&mapping->wb_err, since); +} + +/** + * filemap_sample_wb_err - sample the current errseq_t to test for later errors + * @mapping: mapping to be sampled + * + * Writeback errors are always reported relative to a particular sample point + * in the past. This function provides those sample points. + */ +static inline errseq_t filemap_sample_wb_err(struct address_space *mapping) +{ + return errseq_sample(&mapping->wb_err); +} + extern int vfs_fsync_range(struct file *file, loff_t start, loff_t end, int datasync); extern int vfs_fsync(struct file *file, int datasync); diff --git a/include/trace/events/filemap.h b/include/trace/events/filemap.h index 42febb6bc1d5..ff91325b8123 100644 --- a/include/trace/events/filemap.h +++ b/include/trace/events/filemap.h @@ -10,6 +10,7 @@ #include <linux/memcontrol.h> #include <linux/device.h> #include <linux/kdev_t.h> +#include <linux/errseq.h> DECLARE_EVENT_CLASS(mm_filemap_op_page_cache, @@ -52,6 +53,62 @@ DEFINE_EVENT(mm_filemap_op_page_cache, mm_filemap_add_to_page_cache, TP_ARGS(page) ); +TRACE_EVENT(filemap_set_wb_err, + TP_PROTO(struct address_space *mapping, errseq_t eseq), + + TP_ARGS(mapping, eseq), + + TP_STRUCT__entry( + __field(unsigned long, i_ino) + __field(dev_t, s_dev) + __field(errseq_t, errseq) + ), + + TP_fast_assign( + __entry->i_ino = mapping->host->i_ino; + __entry->errseq = eseq; + if (mapping->host->i_sb) + __entry->s_dev = mapping->host->i_sb->s_dev; + else + __entry->s_dev = mapping->host->i_rdev; + ), + + TP_printk("dev=%d:%d ino=0x%lx errseq=0x%x", + MAJOR(__entry->s_dev), MINOR(__entry->s_dev), + __entry->i_ino, __entry->errseq) +); + +TRACE_EVENT(file_check_and_advance_wb_err, + TP_PROTO(struct file *file, errseq_t old), + + TP_ARGS(file, old), + + TP_STRUCT__entry( + __field(struct file *, file); + __field(unsigned long, i_ino) + __field(dev_t, s_dev) + __field(errseq_t, old) + __field(errseq_t, new) + ), + + TP_fast_assign( + __entry->file = file; + __entry->i_ino = file->f_mapping->host->i_ino; + if (file->f_mapping->host->i_sb) + __entry->s_dev = + file->f_mapping->host->i_sb->s_dev; + else + __entry->s_dev = + file->f_mapping->host->i_rdev; + __entry->old = old; + __entry->new = file->f_wb_err; + ), + + TP_printk("file=%p dev=%d:%d ino=0x%lx old=0x%x new=0x%x", + __entry->file, MAJOR(__entry->s_dev), + MINOR(__entry->s_dev), __entry->i_ino, __entry->old, + __entry->new) +); #endif /* _TRACE_FILEMAP_H */ /* This part must be outside protection */ diff --git a/mm/filemap.c b/mm/filemap.c index eb99b5f23c61..5d03381dc0e0 100644 --- a/mm/filemap.c +++ b/mm/filemap.c @@ -553,6 +553,92 @@ int filemap_write_and_wait_range(struct address_space *mapping, } EXPORT_SYMBOL(filemap_write_and_wait_range); +void __filemap_set_wb_err(struct address_space *mapping, int err) +{ + errseq_t eseq = __errseq_set(&mapping->wb_err, err); + + trace_filemap_set_wb_err(mapping, eseq); +} +EXPORT_SYMBOL(__filemap_set_wb_err); + +/** + * file_check_and_advance_wb_err - report wb error (if any) that was previously + * and advance wb_err to current one + * @file: struct file on which the error is being reported + * + * When userland calls fsync (or something like nfsd does the equivalent), we + * want to report any writeback errors that occurred since the last fsync (or + * since the file was opened if there haven't been any). + * + * Grab the wb_err from the mapping. If it matches what we have in the file, + * then just quickly return 0. The file is all caught up. + * + * If it doesn't match, then take the mapping value, set the "seen" flag in + * it and try to swap it into place. If it works, or another task beat us + * to it with the new value, then update the f_wb_err and return the error + * portion. The error at this point must be reported via proper channels + * (a'la fsync, or NFS COMMIT operation, etc.). + * + * While we handle mapping->wb_err with atomic operations, the f_wb_err + * value is protected by the f_lock since we must ensure that it reflects + * the latest value swapped in for this file descriptor. + */ +int file_check_and_advance_wb_err(struct file *file) +{ + int err = 0; + errseq_t old = READ_ONCE(file->f_wb_err); + struct address_space *mapping = file->f_mapping; + + /* Locklessly handle the common case where nothing has changed */ + if (errseq_check(&mapping->wb_err, old)) { + /* Something changed, must use slow path */ + spin_lock(&file->f_lock); + old = file->f_wb_err; + err = errseq_check_and_advance(&mapping->wb_err, + &file->f_wb_err); + trace_file_check_and_advance_wb_err(file, old); + spin_unlock(&file->f_lock); + } + return err; +} +EXPORT_SYMBOL(file_check_and_advance_wb_err); + +/** + * file_write_and_wait_range - write out & wait on a file range + * @file: file pointing to address_space with pages + * @lstart: offset in bytes where the range starts + * @lend: offset in bytes where the range ends (inclusive) + * + * Write out and wait upon file offsets lstart->lend, inclusive. + * + * Note that @lend is inclusive (describes the last byte to be written) so + * that this function can be used to write to the very end-of-file (end = -1). + * + * After writing out and waiting on the data, we check and advance the + * f_wb_err cursor to the latest value, and return any errors detected there. + */ +int file_write_and_wait_range(struct file *file, loff_t lstart, loff_t lend) +{ + int err = 0; + struct address_space *mapping = file->f_mapping; + + if ((!dax_mapping(mapping) && mapping->nrpages) || + (dax_mapping(mapping) && mapping->nrexceptional)) { + int err2; + + err = __filemap_fdatawrite_range(mapping, lstart, lend, + WB_SYNC_ALL); + /* See comment of filemap_write_and_wait() */ + if (err != -EIO) + __filemap_fdatawait_range(mapping, lstart, lend); + err2 = file_check_and_advance_wb_err(file); + if (!err) + err = err2; + } + return err; +} +EXPORT_SYMBOL(file_write_and_wait_range); + /** * replace_page_cache_page - replace a pagecache page with a new one * @old: page to be replaced -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> When a writeback error occurs, we want later callers to be able to pick up that fact when they go to wait on that writeback to complete. Traditionally, we've used AS_EIO/AS_ENOSPC flags to track that, but that's problematic since only one "checker" will be informed when an error occurs. In later patches, we're going to want to convert many of these callers to check for errors since a well-defined point in time. For now, ensure that we can handle both sorts of checks by both setting errors in both places when there is a writeback failure. Signed-off-by: Jeff Layton <jlayton@redhat.com> --- include/linux/pagemap.h | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h index 316a19f6b635..28acc94e0f81 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h @@ -28,14 +28,33 @@ enum mapping_flags { AS_NO_WRITEBACK_TAGS = 5, }; +/** + * mapping_set_error - record a writeback error in the address_space + * @mapping - the mapping in which an error should be set + * @error - the error to set in the mapping + * + * When writeback fails in some way, we must record that error so that + * userspace can be informed when fsync and the like are called. We endeavor + * to report errors on any file that was open at the time of the error. Some + * internal callers also need to know when writeback errors have occurred. + * + * When a writeback error occurs, most filesystems will want to call + * mapping_set_error to record the error in the mapping so that it can be + * reported when the application calls fsync(2). + */ static inline void mapping_set_error(struct address_space *mapping, int error) { - if (unlikely(error)) { - if (error == -ENOSPC) - set_bit(AS_ENOSPC, &mapping->flags); - else - set_bit(AS_EIO, &mapping->flags); - } + if (likely(!error)) + return; + + /* Record in wb_err for checkers using errseq_t based tracking */ + filemap_set_wb_err(mapping, error); + + /* Record it in flags for now, for legacy callers */ + if (error == -ENOSPC) + set_bit(AS_ENOSPC, &mapping->flags); + else + set_bit(AS_EIO, &mapping->flags); } static inline void mapping_set_unevictable(struct address_space *mapping) -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> Let's try to make this extra clear for fs authors. Cc: Jan Kara <jack@suse.cz> Signed-off-by: Jeff Layton <jlayton@redhat.com> --- Documentation/filesystems/vfs.txt | 43 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt index f42b90687d40..1366043b3942 100644 --- a/Documentation/filesystems/vfs.txt +++ b/Documentation/filesystems/vfs.txt @@ -576,7 +576,42 @@ should clear PG_Dirty and set PG_Writeback. It can be actually written at any point after PG_Dirty is clear. Once it is known to be safe, PG_Writeback is cleared. -Writeback makes use of a writeback_control structure... +Writeback makes use of a writeback_control structure to direct the +operations. This gives the the writepage and writepages operations some +information about the nature of and reason for the writeback request, +and the constraints under which it is being done. It is also used to +return information back to the caller about the result of a writepage or +writepages request. + +Handling errors during writeback +-------------------------------- +Most applications that utilize the pagecache will periodically call +fsync to ensure that data written has made it to the backing store. +When there is an error during writeback, they expect that error to be +reported when fsync is called. After an error has been reported on one +fsync, subsequent fsync calls on the same file descriptor should return +0, unless further writeback errors have occurred since the previous +fsync. + +Ideally, the kernel would report an error only on file descriptions on +which writes were done that subsequently failed to be written back. The +generic pagecache infrastructure does not track the file descriptions +that have dirtied each individual page however, so determining which +file descriptors should get back an error is not possible. + +Instead, the generic writeback error tracking infrastructure in the +kernel settles for reporting errors to fsync on all file descriptions +that were open at the time that the error occurred. In a situation with +multiple writers, all of them will get back an error on a subsequent fsync, +even if all of the writes done through that particular file descriptor +succeeded (or even if there were no writes on that file descriptor at all). + +Filesystems that wish to use this infrastructure should call +mapping_set_error to record the error in the address_space when it +occurs. Then, at the end of their fsync operation, they should call +file_check_and_advance_wb_err to ensure that the struct file's error +cursor has advanced to the correct point in the stream of errors emitted +by the backing device(s). struct address_space_operations ------------------------------- @@ -804,7 +839,8 @@ struct address_space_operations { The File Object =============== -A file object represents a file opened by a process. +A file object represents a file opened by a process. This is also known +as an "open file description" in POSIX parlance. struct file_operations @@ -887,7 +923,8 @@ otherwise noted. release: called when the last reference to an open file is closed - fsync: called by the fsync(2) system call + fsync: called by the fsync(2) system call. Also see the section above + entitled "Handling errors during writeback". fasync: called by the fcntl(2) system call when asynchronous (non-blocking) mode is enabled for a file -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> Jan Kara's description for this patch is much better than mine, so I'm quoting it verbatim here: DAX currently doesn't set errors in the mapping when cache flushing fails in dax_writeback_mapping_range(). Since this function can get called only from fsync(2) or sync(2), this is actually as good as it can currently get since we correctly propagate the error up from dax_writeback_mapping_range() to filemap_fdatawrite() However, in the future better writeback error handling will enable us to properly report these errors on fsync(2) even if there are multiple file descriptors open against the file or if sync(2) gets called before fsync(2). So convert DAX to using standard error reporting through the mapping. Signed-off-by: Jeff Layton <jlayton@redhat.com> Reviewed-by: Jan Kara <jack@suse.cz> Reviewed-by: Christoph Hellwig <hch@lst.de> Reviewed-and-tested-by: Ross Zwisler <ross.zwisler@linux.intel.com> --- fs/dax.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/dax.c b/fs/dax.c index c22eaf162f95..441280e15d5b 100644 --- a/fs/dax.c +++ b/fs/dax.c @@ -856,8 +856,10 @@ int dax_writeback_mapping_range(struct address_space *mapping, ret = dax_writeback_one(bdev, dax_dev, mapping, indices[i], pvec.pages[i]); - if (ret < 0) + if (ret < 0) { + mapping_set_error(mapping, ret); goto out; + } } } out: -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> This is a very minimal conversion to errseq_t based error tracking for raw block device access. Just have it use the standard file_write_and_wait_range call. Note that there are internal callers that call sync_blockdev and the like that are not affected by this. They'll continue to use the AS_EIO/AS_ENOSPC flags for error reporting like they always have for now. Signed-off-by: Jeff Layton <jlayton@redhat.com> --- fs/block_dev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/block_dev.c b/fs/block_dev.c index 4d62fe771587..bcb0be6e423a 100644 --- a/fs/block_dev.c +++ b/fs/block_dev.c @@ -624,7 +624,7 @@ int blkdev_fsync(struct file *filp, loff_t start, loff_t end, int datasync) struct block_device *bdev = I_BDEV(bd_inode); int error; - error = filemap_write_and_wait_range(filp->f_mapping, start, end); + error = file_write_and_wait_range(filp, start, end); if (error) return error; -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> Signed-off-by: Jeff Layton <jlayton@redhat.com> --- fs/libfs.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/libfs.c b/fs/libfs.c index 1dec90819366..3aabe553fc45 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -974,7 +974,7 @@ int __generic_file_fsync(struct file *file, loff_t start, loff_t end, int err; int ret; - err = filemap_write_and_wait_range(inode->i_mapping, start, end); + err = file_write_and_wait_range(file, start, end); if (err) return err; @@ -991,8 +991,11 @@ int __generic_file_fsync(struct file *file, loff_t start, loff_t end, out: inode_unlock(inode); - err = filemap_check_errors(inode->i_mapping); - return ret ? ret : err; + /* check and advance again to catch errors after syncing out buffers */ + err = file_check_and_advance_wb_err(file); + if (ret == 0) + ret = err; + return ret; } EXPORT_SYMBOL(__generic_file_fsync); -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> Add a call to filemap_report_wb_err at the end of ext4_sync_file. This will ensure that we check and advance the errseq_t in the file, which allows us to track and report errors on all open fds when they occur. Note that metadata writeback errors are not yet reported on all fds at this point. That will be added in a later patch. Signed-off-by: Jeff Layton <jlayton@redhat.com> --- fs/ext4/fsync.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/fs/ext4/fsync.c b/fs/ext4/fsync.c index 9d549608fd30..d7cf0934c71c 100644 --- a/fs/ext4/fsync.c +++ b/fs/ext4/fsync.c @@ -100,8 +100,10 @@ int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync) tid_t commit_tid; bool needs_barrier = false; - if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb)))) - return -EIO; + if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb)))) { + ret = -EIO; + goto out; + } J_ASSERT(ext4_journal_current_handle() == NULL); @@ -124,9 +126,10 @@ int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync) goto out; } - ret = filemap_write_and_wait_range(inode->i_mapping, start, end); + ret = file_write_and_wait_range(file, start, end); if (ret) - return ret; + goto out; + /* * data=writeback,ordered: * The caller's filemap_fdatawrite()/wait will sync the data. @@ -152,7 +155,7 @@ int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync) needs_barrier = true; ret = jbd2_complete_transaction(journal, commit_tid); if (needs_barrier) { - issue_flush: +issue_flush: err = blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL, NULL); if (!ret) ret = err; -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> Just check and advance the data errseq_t in struct file before before returning from fsync on normal files. Internal filemap_* callers are left as-is. Signed-off-by: Jeff Layton <jlayton@redhat.com> --- fs/xfs/xfs_file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c index 5fb5a0958a14..6600b264b0b6 100644 --- a/fs/xfs/xfs_file.c +++ b/fs/xfs/xfs_file.c @@ -140,7 +140,7 @@ xfs_file_fsync( trace_xfs_file_fsync(ip); - error = filemap_write_and_wait_range(inode->i_mapping, start, end); + error = file_write_and_wait_range(file, start, end); if (error) return error; -- 2.13.0
From: Jeff Layton <jlayton@redhat.com> Just check and advance the errseq_t in the file before returning. Internal callers of filemap_* functions are left as-is. Signed-off-by: Jeff Layton <jlayton@redhat.com> --- fs/btrfs/file.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c index da1096eb1a40..1f57e1a523d9 100644 --- a/fs/btrfs/file.c +++ b/fs/btrfs/file.c @@ -2011,7 +2011,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) struct btrfs_root *root = BTRFS_I(inode)->root; struct btrfs_trans_handle *trans; struct btrfs_log_ctx ctx; - int ret = 0; + int ret = 0, err; bool full_sync = 0; u64 len; @@ -2030,7 +2030,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) */ ret = start_ordered_ops(inode, start, end); if (ret) - return ret; + goto out; inode_lock(inode); atomic_inc(&root->log_batch); @@ -2227,6 +2227,9 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync) ret = btrfs_end_transaction(trans); } out: + err = file_check_and_advance_wb_err(file); + if (!ret) + ret = err; return ret > 0 ? -EIO : ret; } -- 2.13.0
On Thu, 2017-06-29 at 09:19 -0400, jlayton@kernel.org wrote: > From: Jeff Layton <jlayton@redhat.com> > > Most filesystems currently use mapping_set_error and > filemap_check_errors for setting and reporting/clearing writeback errors > at the mapping level. filemap_check_errors is indirectly called from > most of the filemap_fdatawait_* functions and from > filemap_write_and_wait*. These functions are called from all sorts of > contexts to wait on writeback to finish -- e.g. mostly in fsync, but > also in truncate calls, getattr, etc. > > The non-fsync callers are problematic. We should be reporting writeback > errors during fsync, but many places spread over the tree clear out > errors before they can be properly reported, or report errors at > nonsensical times. > > If I get -EIO on a stat() call, there is no reason for me to assume that > it is because some previous writeback failed. The fact that it also > clears out the error such that a subsequent fsync returns 0 is a bug, > and a nasty one since that's potentially silent data corruption. > > This patch adds a small bit of new infrastructure for setting and > reporting errors during address_space writeback. While the above was my > original impetus for adding this, I think it's also the case that > current fsync semantics are just problematic for userland. Most > applications that call fsync do so to ensure that the data they wrote > has hit the backing store. > > In the case where there are multiple writers to the file at the same > time, this is really hard to determine. The first one to call fsync will > see any stored error, and the rest get back 0. The processes with open > fds may not be associated with one another in any way. They could even > be in different containers, so ensuring coordination between all fsync > callers is not really an option. > > One way to remedy this would be to track what file descriptor was used > to dirty the file, but that's rather cumbersome and would likely be > slow. However, there is a simpler way to improve the semantics here > without incurring too much overhead. > > This set adds an errseq_t to struct address_space, and a corresponding > one is added to struct file. Writeback errors are recorded in the > mapping's errseq_t, and the one in struct file is used as the "since" > value. > > This changes the semantics of the Linux fsync implementation such that > applications can now use it to determine whether there were any > writeback errors since fsync(fd) was last called (or since the file was > opened in the case of fsync having never been called). > > Note that those writeback errors may have occurred when writing data > that was dirtied via an entirely different fd, but that's the case now > with the current mapping_set_error/filemap_check_error infrastructure. > This will at least prevent you from getting a false report of success. > > The new behavior is still consistent with the POSIX spec, and is more > reliable for application developers. This patch just adds some basic > infrastructure for doing this, and ensures that the f_wb_err "cursor" > is properly set when a file is opened. Later patches will change the > existing code to use this new infrastructure for reporting errors at > fsync time. > > Signed-off-by: Jeff Layton <jlayton@redhat.com> > Reviewed-by: Jan Kara <jack@suse.cz> > --- > drivers/dax/device.c | 1 + > fs/block_dev.c | 1 + > fs/file_table.c | 1 + > fs/open.c | 3 ++ > include/linux/fs.h | 60 ++++++++++++++++++++++++++++- > include/trace/events/filemap.h | 57 ++++++++++++++++++++++++++++ > mm/filemap.c | 86 ++++++++++++++++++++++++++++++++++++++++++ > 7 files changed, 208 insertions(+), 1 deletion(-) > > diff --git a/drivers/dax/device.c b/drivers/dax/device.c > index 006e657dfcb9..12943d19bfc4 100644 > --- a/drivers/dax/device.c > +++ b/drivers/dax/device.c > @@ -499,6 +499,7 @@ static int dax_open(struct inode *inode, struct file *filp) > inode->i_mapping = __dax_inode->i_mapping; > inode->i_mapping->host = __dax_inode; > filp->f_mapping = inode->i_mapping; > + filp->f_wb_err = filemap_sample_wb_err(filp->f_mapping); > filp->private_data = dev_dax; > inode->i_flags = S_DAX; > > diff --git a/fs/block_dev.c b/fs/block_dev.c > index 519599dddd36..4d62fe771587 100644 > --- a/fs/block_dev.c > +++ b/fs/block_dev.c > @@ -1743,6 +1743,7 @@ static int blkdev_open(struct inode * inode, struct file * filp) > return -ENOMEM; > > filp->f_mapping = bdev->bd_inode->i_mapping; > + filp->f_wb_err = filemap_sample_wb_err(filp->f_mapping); > > return blkdev_get(bdev, filp->f_mode, filp); > } > diff --git a/fs/file_table.c b/fs/file_table.c > index 954d510b765a..72e861a35a7f 100644 > --- a/fs/file_table.c > +++ b/fs/file_table.c > @@ -168,6 +168,7 @@ struct file *alloc_file(const struct path *path, fmode_t mode, > file->f_path = *path; > file->f_inode = path->dentry->d_inode; > file->f_mapping = path->dentry->d_inode->i_mapping; > + file->f_wb_err = filemap_sample_wb_err(file->f_mapping); > if ((mode & FMODE_READ) && > likely(fop->read || fop->read_iter)) > mode |= FMODE_CAN_READ; > diff --git a/fs/open.c b/fs/open.c > index cd0c5be8d012..280d4a963791 100644 > --- a/fs/open.c > +++ b/fs/open.c > @@ -707,6 +707,9 @@ static int do_dentry_open(struct file *f, > f->f_inode = inode; > f->f_mapping = inode->i_mapping; > > + /* Ensure that we skip any errors that predate opening of the file */ > + f->f_wb_err = filemap_sample_wb_err(f->f_mapping); > + > if (unlikely(f->f_flags & O_PATH)) { > f->f_mode = FMODE_PATH; > f->f_op = &empty_fops; > diff --git a/include/linux/fs.h b/include/linux/fs.h > index 74872c0f1c07..b524fd442057 100644 > --- a/include/linux/fs.h > +++ b/include/linux/fs.h > @@ -30,7 +30,7 @@ > #include <linux/percpu-rwsem.h> > #include <linux/workqueue.h> > #include <linux/delayed_call.h> > - > +#include <linux/errseq.h> > #include <asm/byteorder.h> > #include <uapi/linux/fs.h> > > @@ -392,6 +392,7 @@ struct address_space { > gfp_t gfp_mask; /* implicit gfp mask for allocations */ > struct list_head private_list; /* ditto */ > void *private_data; /* ditto */ > + errseq_t wb_err; > } __attribute__((aligned(sizeof(long)))); > /* > * On most architectures that alignment is already the case; but > @@ -846,6 +847,7 @@ struct file { > * Must not be taken from IRQ context. > */ > spinlock_t f_lock; > + errseq_t f_wb_err; Ahh, this field needs to be moved to the end of the struct to avoid conflict with Jens' work. I'll fix that in my linux-next branch. > atomic_long_t f_count; > unsigned int f_flags; > fmode_t f_mode; > @@ -2520,6 +2522,62 @@ extern int filemap_fdatawrite_range(struct address_space *mapping, > loff_t start, loff_t end); > extern int filemap_check_errors(struct address_space *mapping); > > +extern void __filemap_set_wb_err(struct address_space *mapping, int err); > +extern int __must_check file_check_and_advance_wb_err(struct file *file); > +extern int __must_check file_write_and_wait_range(struct file *file, > + loff_t start, loff_t end); > + > +/** > + * filemap_set_wb_err - set a writeback error on an address_space > + * @mapping: mapping in which to set writeback error > + * @err: error to be set in mapping > + * > + * When writeback fails in some way, we must record that error so that > + * userspace can be informed when fsync and the like are called. We endeavor > + * to report errors on any file that was open at the time of the error. Some > + * internal callers also need to know when writeback errors have occurred. > + * > + * When a writeback error occurs, most filesystems will want to call > + * filemap_set_wb_err to record the error in the mapping so that it will be > + * automatically reported whenever fsync is called on the file. > + * > + * FIXME: mention FS_* flag here? > + */ > +static inline void filemap_set_wb_err(struct address_space *mapping, int err) > +{ > + /* Fastpath for common case of no error */ > + if (unlikely(err)) > + __filemap_set_wb_err(mapping, err); > +} > + > +/** > + * filemap_check_wb_error - has an error occurred since the mark was sampled? > + * @mapping: mapping to check for writeback errors > + * @since: previously-sampled errseq_t > + * > + * Grab the errseq_t value from the mapping, and see if it has changed "since" > + * the given value was sampled. > + * > + * If it has then report the latest error set, otherwise return 0. > + */ > +static inline int filemap_check_wb_err(struct address_space *mapping, > + errseq_t since) > +{ > + return errseq_check(&mapping->wb_err, since); > +} > + > +/** > + * filemap_sample_wb_err - sample the current errseq_t to test for later errors > + * @mapping: mapping to be sampled > + * > + * Writeback errors are always reported relative to a particular sample point > + * in the past. This function provides those sample points. > + */ > +static inline errseq_t filemap_sample_wb_err(struct address_space *mapping) > +{ > + return errseq_sample(&mapping->wb_err); > +} > + > extern int vfs_fsync_range(struct file *file, loff_t start, loff_t end, > int datasync); > extern int vfs_fsync(struct file *file, int datasync); > diff --git a/include/trace/events/filemap.h b/include/trace/events/filemap.h > index 42febb6bc1d5..ff91325b8123 100644 > --- a/include/trace/events/filemap.h > +++ b/include/trace/events/filemap.h > @@ -10,6 +10,7 @@ > #include <linux/memcontrol.h> > #include <linux/device.h> > #include <linux/kdev_t.h> > +#include <linux/errseq.h> > > DECLARE_EVENT_CLASS(mm_filemap_op_page_cache, > > @@ -52,6 +53,62 @@ DEFINE_EVENT(mm_filemap_op_page_cache, mm_filemap_add_to_page_cache, > TP_ARGS(page) > ); > > +TRACE_EVENT(filemap_set_wb_err, > + TP_PROTO(struct address_space *mapping, errseq_t eseq), > + > + TP_ARGS(mapping, eseq), > + > + TP_STRUCT__entry( > + __field(unsigned long, i_ino) > + __field(dev_t, s_dev) > + __field(errseq_t, errseq) > + ), > + > + TP_fast_assign( > + __entry->i_ino = mapping->host->i_ino; > + __entry->errseq = eseq; > + if (mapping->host->i_sb) > + __entry->s_dev = mapping->host->i_sb->s_dev; > + else > + __entry->s_dev = mapping->host->i_rdev; > + ), > + > + TP_printk("dev=%d:%d ino=0x%lx errseq=0x%x", > + MAJOR(__entry->s_dev), MINOR(__entry->s_dev), > + __entry->i_ino, __entry->errseq) > +); > + > +TRACE_EVENT(file_check_and_advance_wb_err, > + TP_PROTO(struct file *file, errseq_t old), > + > + TP_ARGS(file, old), > + > + TP_STRUCT__entry( > + __field(struct file *, file); > + __field(unsigned long, i_ino) > + __field(dev_t, s_dev) > + __field(errseq_t, old) > + __field(errseq_t, new) > + ), > + > + TP_fast_assign( > + __entry->file = file; > + __entry->i_ino = file->f_mapping->host->i_ino; > + if (file->f_mapping->host->i_sb) > + __entry->s_dev = > + file->f_mapping->host->i_sb->s_dev; > + else > + __entry->s_dev = > + file->f_mapping->host->i_rdev; > + __entry->old = old; > + __entry->new = file->f_wb_err; > + ), > + > + TP_printk("file=%p dev=%d:%d ino=0x%lx old=0x%x new=0x%x", > + __entry->file, MAJOR(__entry->s_dev), > + MINOR(__entry->s_dev), __entry->i_ino, __entry->old, > + __entry->new) > +); > #endif /* _TRACE_FILEMAP_H */ > > /* This part must be outside protection */ > diff --git a/mm/filemap.c b/mm/filemap.c > index eb99b5f23c61..5d03381dc0e0 100644 > --- a/mm/filemap.c > +++ b/mm/filemap.c > @@ -553,6 +553,92 @@ int filemap_write_and_wait_range(struct address_space *mapping, > } > EXPORT_SYMBOL(filemap_write_and_wait_range); > > +void __filemap_set_wb_err(struct address_space *mapping, int err) > +{ > + errseq_t eseq = __errseq_set(&mapping->wb_err, err); > + > + trace_filemap_set_wb_err(mapping, eseq); > +} > +EXPORT_SYMBOL(__filemap_set_wb_err); > + > +/** > + * file_check_and_advance_wb_err - report wb error (if any) that was previously > + * and advance wb_err to current one > + * @file: struct file on which the error is being reported > + * > + * When userland calls fsync (or something like nfsd does the equivalent), we > + * want to report any writeback errors that occurred since the last fsync (or > + * since the file was opened if there haven't been any). > + * > + * Grab the wb_err from the mapping. If it matches what we have in the file, > + * then just quickly return 0. The file is all caught up. > + * > + * If it doesn't match, then take the mapping value, set the "seen" flag in > + * it and try to swap it into place. If it works, or another task beat us > + * to it with the new value, then update the f_wb_err and return the error > + * portion. The error at this point must be reported via proper channels > + * (a'la fsync, or NFS COMMIT operation, etc.). > + * > + * While we handle mapping->wb_err with atomic operations, the f_wb_err > + * value is protected by the f_lock since we must ensure that it reflects > + * the latest value swapped in for this file descriptor. > + */ > +int file_check_and_advance_wb_err(struct file *file) > +{ > + int err = 0; > + errseq_t old = READ_ONCE(file->f_wb_err); > + struct address_space *mapping = file->f_mapping; > + > + /* Locklessly handle the common case where nothing has changed */ > + if (errseq_check(&mapping->wb_err, old)) { > + /* Something changed, must use slow path */ > + spin_lock(&file->f_lock); > + old = file->f_wb_err; > + err = errseq_check_and_advance(&mapping->wb_err, > + &file->f_wb_err); > + trace_file_check_and_advance_wb_err(file, old); > + spin_unlock(&file->f_lock); > + } > + return err; > +} > +EXPORT_SYMBOL(file_check_and_advance_wb_err); > + > +/** > + * file_write_and_wait_range - write out & wait on a file range > + * @file: file pointing to address_space with pages > + * @lstart: offset in bytes where the range starts > + * @lend: offset in bytes where the range ends (inclusive) > + * > + * Write out and wait upon file offsets lstart->lend, inclusive. > + * > + * Note that @lend is inclusive (describes the last byte to be written) so > + * that this function can be used to write to the very end-of-file (end = -1). > + * > + * After writing out and waiting on the data, we check and advance the > + * f_wb_err cursor to the latest value, and return any errors detected there. > + */ > +int file_write_and_wait_range(struct file *file, loff_t lstart, loff_t lend) > +{ > + int err = 0; > + struct address_space *mapping = file->f_mapping; > + > + if ((!dax_mapping(mapping) && mapping->nrpages) || > + (dax_mapping(mapping) && mapping->nrexceptional)) { > + int err2; > + > + err = __filemap_fdatawrite_range(mapping, lstart, lend, > + WB_SYNC_ALL); > + /* See comment of filemap_write_and_wait() */ > + if (err != -EIO) > + __filemap_fdatawait_range(mapping, lstart, lend); > + err2 = file_check_and_advance_wb_err(file); > + if (!err) > + err = err2; > + } > + return err; > +} > +EXPORT_SYMBOL(file_write_and_wait_range); > + > /** > * replace_page_cache_page - replace a pagecache page with a new one > * @old: page to be replaced -- Jeff Layton <jlayton@redhat.com>
> - if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb)))) > - return -EIO; > + if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb)))) { > + ret = -EIO; > + goto out; > + } This just seems to add a call to trace_ext4_sync_file_exit for this case, which seems unrelated to the patch. > if (ret) > - return ret; > + goto out; > + Same here. > /* > * data=writeback,ordered: > * The caller's filemap_fdatawrite()/wait will sync the data. > @@ -152,7 +155,7 @@ int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync) > needs_barrier = true; > ret = jbd2_complete_transaction(journal, commit_tid); > if (needs_barrier) { > - issue_flush: > +issue_flush: > err = blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL, NULL); And while I much prefer your new label placement it also doesn't seem to belong into this patch.
Nice and simple, this looks great! Reviewed-by: Christoph Hellwig <hch@lst.de>
On Thu, Jun 29, 2017 at 09:19:54AM -0400, jlayton@kernel.org wrote:
> From: Jeff Layton <jlayton@redhat.com>
>
> Just check and advance the errseq_t in the file before returning.
> Internal callers of filemap_* functions are left as-is.
>
> Signed-off-by: Jeff Layton <jlayton@redhat.com>
> ---
> fs/btrfs/file.c | 7 +++++--
> 1 file changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
> index da1096eb1a40..1f57e1a523d9 100644
> --- a/fs/btrfs/file.c
> +++ b/fs/btrfs/file.c
> @@ -2011,7 +2011,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
> struct btrfs_root *root = BTRFS_I(inode)->root;
> struct btrfs_trans_handle *trans;
> struct btrfs_log_ctx ctx;
> - int ret = 0;
> + int ret = 0, err;
> bool full_sync = 0;
> u64 len;
>
> @@ -2030,7 +2030,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
> */
> ret = start_ordered_ops(inode, start, end);
> if (ret)
> - return ret;
> + goto out;
>
> inode_lock(inode);
> atomic_inc(&root->log_batch);
> @@ -2227,6 +2227,9 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
> ret = btrfs_end_transaction(trans);
> }
> out:
> + err = file_check_and_advance_wb_err(file);
> + if (!ret)
> + ret = err;
> return ret > 0 ? -EIO : ret;
This means that we'll lose the exact error returned from
start_ordered_ops. Beyond that I can't really provide good feedback
as the btrfs fsync code looks so much different from all the other
fs fsync code..
Looks good, Reviewed-by: Christoph Hellwig <hch@lst.de>
On Thu, Jun 29, 2017 at 09:19:39AM -0400, jlayton@kernel.org wrote:
> From: Jeff Layton <jlayton@redhat.com>
>
> ext2 currently does a test+clear of the AS_EIO flag, which is
> is problematic for some coming changes.
>
> What we really need to do instead is call filemap_check_errors
> in __generic_file_fsync after syncing out the buffers. That
> will be sufficient for this case, and help other callers detect
> these errors properly as well.
>
> With that, we don't need to twiddle it in ext2.
Seems like much of this code is getting replaced later in the
series..
On Thu, Jun 29, 2017 at 09:19:48AM -0400, jlayton@kernel.org wrote: > From: Jeff Layton <jlayton@redhat.com> > > Let's try to make this extra clear for fs authors. > > Cc: Jan Kara <jack@suse.cz> > Signed-off-by: Jeff Layton <jlayton@redhat.com> > --- > Documentation/filesystems/vfs.txt | 43 ++++++++++++++++++++++++++++++++++++--- > 1 file changed, 40 insertions(+), 3 deletions(-) > > diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt > index f42b90687d40..1366043b3942 100644 > --- a/Documentation/filesystems/vfs.txt > +++ b/Documentation/filesystems/vfs.txt > @@ -576,7 +576,42 @@ should clear PG_Dirty and set PG_Writeback. It can be actually > written at any point after PG_Dirty is clear. Once it is known to be > safe, PG_Writeback is cleared. > > -Writeback makes use of a writeback_control structure... > +Writeback makes use of a writeback_control structure to direct the > +operations. This gives the the writepage and writepages operations some > +information about the nature of and reason for the writeback request, > +and the constraints under which it is being done. It is also used to > +return information back to the caller about the result of a writepage or > +writepages request. > + > +Handling errors during writeback > +-------------------------------- > +Most applications that utilize the pagecache will periodically call > +fsync to ensure that data written has made it to the backing store. /me wonders if this sentence ought to be worded more strongly, e.g. "Applications that utilize the pagecache must call a data synchronization syscall such as fsync, fdatasync, or msync to ensure that data written has made it to the backing store." I'm also wondering -- fdatasync and msync will also report any writeback errors that have happened anywhere (like fsync), since they all map to vfs_fsync_range, correct? If so, I think it worth it to state explicitly that the other *sync methods behave the same as fsync w.r.t. writeback error reporting. --D > +When there is an error during writeback, they expect that error to be > +reported when fsync is called. After an error has been reported on one > +fsync, subsequent fsync calls on the same file descriptor should return > +0, unless further writeback errors have occurred since the previous > +fsync. > + > +Ideally, the kernel would report an error only on file descriptions on > +which writes were done that subsequently failed to be written back. The > +generic pagecache infrastructure does not track the file descriptions > +that have dirtied each individual page however, so determining which > +file descriptors should get back an error is not possible. > + > +Instead, the generic writeback error tracking infrastructure in the > +kernel settles for reporting errors to fsync on all file descriptions > +that were open at the time that the error occurred. In a situation with > +multiple writers, all of them will get back an error on a subsequent fsync, > +even if all of the writes done through that particular file descriptor > +succeeded (or even if there were no writes on that file descriptor at all). > + > +Filesystems that wish to use this infrastructure should call > +mapping_set_error to record the error in the address_space when it > +occurs. Then, at the end of their fsync operation, they should call > +file_check_and_advance_wb_err to ensure that the struct file's error > +cursor has advanced to the correct point in the stream of errors emitted > +by the backing device(s). > > struct address_space_operations > ------------------------------- > @@ -804,7 +839,8 @@ struct address_space_operations { > The File Object > =============== > > -A file object represents a file opened by a process. > +A file object represents a file opened by a process. This is also known > +as an "open file description" in POSIX parlance. > > > struct file_operations > @@ -887,7 +923,8 @@ otherwise noted. > > release: called when the last reference to an open file is closed > > - fsync: called by the fsync(2) system call > + fsync: called by the fsync(2) system call. Also see the section above > + entitled "Handling errors during writeback". > > fasync: called by the fcntl(2) system call when asynchronous > (non-blocking) mode is enabled for a file > -- > 2.13.0 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-xfs" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu, Jun 29, 2017 at 09:19:53AM -0400, jlayton@kernel.org wrote: > From: Jeff Layton <jlayton@redhat.com> > > Just check and advance the data errseq_t in struct file before > before returning from fsync on normal files. Internal filemap_* > callers are left as-is. > > Signed-off-by: Jeff Layton <jlayton@redhat.com> Looks ok, Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com> --D > --- > fs/xfs/xfs_file.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c > index 5fb5a0958a14..6600b264b0b6 100644 > --- a/fs/xfs/xfs_file.c > +++ b/fs/xfs/xfs_file.c > @@ -140,7 +140,7 @@ xfs_file_fsync( > > trace_xfs_file_fsync(ip); > > - error = filemap_write_and_wait_range(inode->i_mapping, start, end); > + error = file_write_and_wait_range(file, start, end); > if (error) > return error; > > -- > 2.13.0 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-xfs" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html
On Thu, 2017-06-29 at 09:19 -0400, jlayton@kernel.org wrote: > From: Jeff Layton <jlayton@redhat.com> > > Most filesystems currently use mapping_set_error and > filemap_check_errors for setting and reporting/clearing writeback errors > at the mapping level. filemap_check_errors is indirectly called from > most of the filemap_fdatawait_* functions and from > filemap_write_and_wait*. These functions are called from all sorts of > contexts to wait on writeback to finish -- e.g. mostly in fsync, but > also in truncate calls, getattr, etc. > > The non-fsync callers are problematic. We should be reporting writeback > errors during fsync, but many places spread over the tree clear out > errors before they can be properly reported, or report errors at > nonsensical times. > > If I get -EIO on a stat() call, there is no reason for me to assume that > it is because some previous writeback failed. The fact that it also > clears out the error such that a subsequent fsync returns 0 is a bug, > and a nasty one since that's potentially silent data corruption. > > This patch adds a small bit of new infrastructure for setting and > reporting errors during address_space writeback. While the above was my > original impetus for adding this, I think it's also the case that > current fsync semantics are just problematic for userland. Most > applications that call fsync do so to ensure that the data they wrote > has hit the backing store. > > In the case where there are multiple writers to the file at the same > time, this is really hard to determine. The first one to call fsync will > see any stored error, and the rest get back 0. The processes with open > fds may not be associated with one another in any way. They could even > be in different containers, so ensuring coordination between all fsync > callers is not really an option. > > One way to remedy this would be to track what file descriptor was used > to dirty the file, but that's rather cumbersome and would likely be > slow. However, there is a simpler way to improve the semantics here > without incurring too much overhead. > > This set adds an errseq_t to struct address_space, and a corresponding > one is added to struct file. Writeback errors are recorded in the > mapping's errseq_t, and the one in struct file is used as the "since" > value. > > This changes the semantics of the Linux fsync implementation such that > applications can now use it to determine whether there were any > writeback errors since fsync(fd) was last called (or since the file was > opened in the case of fsync having never been called). > > Note that those writeback errors may have occurred when writing data > that was dirtied via an entirely different fd, but that's the case now > with the current mapping_set_error/filemap_check_error infrastructure. > This will at least prevent you from getting a false report of success. > > The new behavior is still consistent with the POSIX spec, and is more > reliable for application developers. This patch just adds some basic > infrastructure for doing this, and ensures that the f_wb_err "cursor" > is properly set when a file is opened. Later patches will change the > existing code to use this new infrastructure for reporting errors at > fsync time. > > Signed-off-by: Jeff Layton <jlayton@redhat.com> > Reviewed-by: Jan Kara <jack@suse.cz> > --- > drivers/dax/device.c | 1 + > fs/block_dev.c | 1 + > fs/file_table.c | 1 + > fs/open.c | 3 ++ > include/linux/fs.h | 60 ++++++++++++++++++++++++++++- > include/trace/events/filemap.h | 57 ++++++++++++++++++++++++++++ > mm/filemap.c | 86 ++++++++++++++++++++++++++++++++++++++++++ > 7 files changed, 208 insertions(+), 1 deletion(-) > > diff --git a/drivers/dax/device.c b/drivers/dax/device.c > index 006e657dfcb9..12943d19bfc4 100644 > --- a/drivers/dax/device.c > +++ b/drivers/dax/device.c > @@ -499,6 +499,7 @@ static int dax_open(struct inode *inode, struct file *filp) > inode->i_mapping = __dax_inode->i_mapping; > inode->i_mapping->host = __dax_inode; > filp->f_mapping = inode->i_mapping; > + filp->f_wb_err = filemap_sample_wb_err(filp->f_mapping); > filp->private_data = dev_dax; > inode->i_flags = S_DAX; > > diff --git a/fs/block_dev.c b/fs/block_dev.c > index 519599dddd36..4d62fe771587 100644 > --- a/fs/block_dev.c > +++ b/fs/block_dev.c > @@ -1743,6 +1743,7 @@ static int blkdev_open(struct inode * inode, struct file * filp) > return -ENOMEM; > > filp->f_mapping = bdev->bd_inode->i_mapping; > + filp->f_wb_err = filemap_sample_wb_err(filp->f_mapping); > > return blkdev_get(bdev, filp->f_mode, filp); > } > diff --git a/fs/file_table.c b/fs/file_table.c > index 954d510b765a..72e861a35a7f 100644 > --- a/fs/file_table.c > +++ b/fs/file_table.c > @@ -168,6 +168,7 @@ struct file *alloc_file(const struct path *path, fmode_t mode, > file->f_path = *path; > file->f_inode = path->dentry->d_inode; > file->f_mapping = path->dentry->d_inode->i_mapping; > + file->f_wb_err = filemap_sample_wb_err(file->f_mapping); > if ((mode & FMODE_READ) && > likely(fop->read || fop->read_iter)) > mode |= FMODE_CAN_READ; > diff --git a/fs/open.c b/fs/open.c > index cd0c5be8d012..280d4a963791 100644 > --- a/fs/open.c > +++ b/fs/open.c > @@ -707,6 +707,9 @@ static int do_dentry_open(struct file *f, > f->f_inode = inode; > f->f_mapping = inode->i_mapping; > > + /* Ensure that we skip any errors that predate opening of the file */ > + f->f_wb_err = filemap_sample_wb_err(f->f_mapping); > + > if (unlikely(f->f_flags & O_PATH)) { > f->f_mode = FMODE_PATH; > f->f_op = &empty_fops; > diff --git a/include/linux/fs.h b/include/linux/fs.h > index 74872c0f1c07..b524fd442057 100644 > --- a/include/linux/fs.h > +++ b/include/linux/fs.h > @@ -30,7 +30,7 @@ > #include <linux/percpu-rwsem.h> > #include <linux/workqueue.h> > #include <linux/delayed_call.h> > - > +#include <linux/errseq.h> > #include <asm/byteorder.h> > #include <uapi/linux/fs.h> > > @@ -392,6 +392,7 @@ struct address_space { > gfp_t gfp_mask; /* implicit gfp mask for allocations */ > struct list_head private_list; /* ditto */ > void *private_data; /* ditto */ > + errseq_t wb_err; > } __attribute__((aligned(sizeof(long)))); > /* > * On most architectures that alignment is already the case; but > @@ -846,6 +847,7 @@ struct file { > * Must not be taken from IRQ context. > */ > spinlock_t f_lock; > + errseq_t f_wb_err; > atomic_long_t f_count; > unsigned int f_flags; > fmode_t f_mode; > @@ -2520,6 +2522,62 @@ extern int filemap_fdatawrite_range(struct address_space *mapping, > loff_t start, loff_t end); > extern int filemap_check_errors(struct address_space *mapping); > > +extern void __filemap_set_wb_err(struct address_space *mapping, int err); > +extern int __must_check file_check_and_advance_wb_err(struct file *file); > +extern int __must_check file_write_and_wait_range(struct file *file, > + loff_t start, loff_t end); > + > +/** > + * filemap_set_wb_err - set a writeback error on an address_space > + * @mapping: mapping in which to set writeback error > + * @err: error to be set in mapping > + * > + * When writeback fails in some way, we must record that error so that > + * userspace can be informed when fsync and the like are called. We endeavor > + * to report errors on any file that was open at the time of the error. Some > + * internal callers also need to know when writeback errors have occurred. > + * > + * When a writeback error occurs, most filesystems will want to call > + * filemap_set_wb_err to record the error in the mapping so that it will be > + * automatically reported whenever fsync is called on the file. > + * > + * FIXME: mention FS_* flag here? > + */ > +static inline void filemap_set_wb_err(struct address_space *mapping, int err) > +{ > + /* Fastpath for common case of no error */ > + if (unlikely(err)) > + __filemap_set_wb_err(mapping, err); > +} > + > +/** > + * filemap_check_wb_error - has an error occurred since the mark was sampled? > + * @mapping: mapping to check for writeback errors > + * @since: previously-sampled errseq_t > + * > + * Grab the errseq_t value from the mapping, and see if it has changed "since" > + * the given value was sampled. > + * > + * If it has then report the latest error set, otherwise return 0. > + */ > +static inline int filemap_check_wb_err(struct address_space *mapping, > + errseq_t since) > +{ > + return errseq_check(&mapping->wb_err, since); > +} > + > +/** > + * filemap_sample_wb_err - sample the current errseq_t to test for later errors > + * @mapping: mapping to be sampled > + * > + * Writeback errors are always reported relative to a particular sample point > + * in the past. This function provides those sample points. > + */ > +static inline errseq_t filemap_sample_wb_err(struct address_space *mapping) > +{ > + return errseq_sample(&mapping->wb_err); > +} > + > extern int vfs_fsync_range(struct file *file, loff_t start, loff_t end, > int datasync); > extern int vfs_fsync(struct file *file, int datasync); > diff --git a/include/trace/events/filemap.h b/include/trace/events/filemap.h > index 42febb6bc1d5..ff91325b8123 100644 > --- a/include/trace/events/filemap.h > +++ b/include/trace/events/filemap.h > @@ -10,6 +10,7 @@ > #include <linux/memcontrol.h> > #include <linux/device.h> > #include <linux/kdev_t.h> > +#include <linux/errseq.h> > > DECLARE_EVENT_CLASS(mm_filemap_op_page_cache, > > @@ -52,6 +53,62 @@ DEFINE_EVENT(mm_filemap_op_page_cache, mm_filemap_add_to_page_cache, > TP_ARGS(page) > ); > > +TRACE_EVENT(filemap_set_wb_err, > + TP_PROTO(struct address_space *mapping, errseq_t eseq), > + > + TP_ARGS(mapping, eseq), > + > + TP_STRUCT__entry( > + __field(unsigned long, i_ino) > + __field(dev_t, s_dev) > + __field(errseq_t, errseq) > + ), > + > + TP_fast_assign( > + __entry->i_ino = mapping->host->i_ino; > + __entry->errseq = eseq; > + if (mapping->host->i_sb) > + __entry->s_dev = mapping->host->i_sb->s_dev; > + else > + __entry->s_dev = mapping->host->i_rdev; > + ), > + > + TP_printk("dev=%d:%d ino=0x%lx errseq=0x%x", > + MAJOR(__entry->s_dev), MINOR(__entry->s_dev), > + __entry->i_ino, __entry->errseq) > +); > + > +TRACE_EVENT(file_check_and_advance_wb_err, > + TP_PROTO(struct file *file, errseq_t old), > + > + TP_ARGS(file, old), > + > + TP_STRUCT__entry( > + __field(struct file *, file); > + __field(unsigned long, i_ino) > + __field(dev_t, s_dev) > + __field(errseq_t, old) > + __field(errseq_t, new) > + ), > + > + TP_fast_assign( > + __entry->file = file; > + __entry->i_ino = file->f_mapping->host->i_ino; > + if (file->f_mapping->host->i_sb) > + __entry->s_dev = > + file->f_mapping->host->i_sb->s_dev; > + else > + __entry->s_dev = > + file->f_mapping->host->i_rdev; > + __entry->old = old; > + __entry->new = file->f_wb_err; > + ), > + > + TP_printk("file=%p dev=%d:%d ino=0x%lx old=0x%x new=0x%x", > + __entry->file, MAJOR(__entry->s_dev), > + MINOR(__entry->s_dev), __entry->i_ino, __entry->old, > + __entry->new) > +); > #endif /* _TRACE_FILEMAP_H */ > > /* This part must be outside protection */ > diff --git a/mm/filemap.c b/mm/filemap.c > index eb99b5f23c61..5d03381dc0e0 100644 > --- a/mm/filemap.c > +++ b/mm/filemap.c > @@ -553,6 +553,92 @@ int filemap_write_and_wait_range(struct address_space *mapping, > } > EXPORT_SYMBOL(filemap_write_and_wait_range); > > +void __filemap_set_wb_err(struct address_space *mapping, int err) > +{ > + errseq_t eseq = __errseq_set(&mapping->wb_err, err); > + > + trace_filemap_set_wb_err(mapping, eseq); > +} > +EXPORT_SYMBOL(__filemap_set_wb_err); > + > +/** > + * file_check_and_advance_wb_err - report wb error (if any) that was previously > + * and advance wb_err to current one > + * @file: struct file on which the error is being reported > + * > + * When userland calls fsync (or something like nfsd does the equivalent), we > + * want to report any writeback errors that occurred since the last fsync (or > + * since the file was opened if there haven't been any). > + * > + * Grab the wb_err from the mapping. If it matches what we have in the file, > + * then just quickly return 0. The file is all caught up. > + * > + * If it doesn't match, then take the mapping value, set the "seen" flag in > + * it and try to swap it into place. If it works, or another task beat us > + * to it with the new value, then update the f_wb_err and return the error > + * portion. The error at this point must be reported via proper channels > + * (a'la fsync, or NFS COMMIT operation, etc.). > + * > + * While we handle mapping->wb_err with atomic operations, the f_wb_err > + * value is protected by the f_lock since we must ensure that it reflects > + * the latest value swapped in for this file descriptor. > + */ > +int file_check_and_advance_wb_err(struct file *file) > +{ > + int err = 0; > + errseq_t old = READ_ONCE(file->f_wb_err); > + struct address_space *mapping = file->f_mapping; > + > + /* Locklessly handle the common case where nothing has changed */ > + if (errseq_check(&mapping->wb_err, old)) { > + /* Something changed, must use slow path */ > + spin_lock(&file->f_lock); > + old = file->f_wb_err; > + err = errseq_check_and_advance(&mapping->wb_err, > + &file->f_wb_err); > + trace_file_check_and_advance_wb_err(file, old); > + spin_unlock(&file->f_lock); > + } > + return err; > +} > +EXPORT_SYMBOL(file_check_and_advance_wb_err); > + > +/** > + * file_write_and_wait_range - write out & wait on a file range > + * @file: file pointing to address_space with pages > + * @lstart: offset in bytes where the range starts > + * @lend: offset in bytes where the range ends (inclusive) > + * > + * Write out and wait upon file offsets lstart->lend, inclusive. > + * > + * Note that @lend is inclusive (describes the last byte to be written) so > + * that this function can be used to write to the very end-of-file (end = -1). > + * > + * After writing out and waiting on the data, we check and advance the > + * f_wb_err cursor to the latest value, and return any errors detected there. > + */ > +int file_write_and_wait_range(struct file *file, loff_t lstart, loff_t lend) > +{ > + int err = 0; > + struct address_space *mapping = file->f_mapping; > + > + if ((!dax_mapping(mapping) && mapping->nrpages) || > + (dax_mapping(mapping) && mapping->nrexceptional)) { > + int err2; > + > + err = __filemap_fdatawrite_range(mapping, lstart, lend, > + WB_SYNC_ALL); > + /* See comment of filemap_write_and_wait() */ > + if (err != -EIO) > + __filemap_fdatawait_range(mapping, lstart, lend); > + err2 = file_check_and_advance_wb_err(file); > + if (!err) > + err = err2; Braino on my part here. We need to do the check and advance unconditionally in this function to handle the case where the nrpages and/or nrexceptional are 0. Fixed in my tree. > + } > + return err; > +} > +EXPORT_SYMBOL(file_write_and_wait_range); > + > /** > * replace_page_cache_page - replace a pagecache page with a new one > * @old: page to be replaced -- Jeff Layton <jlayton@redhat.com>
On Thu, 2017-06-29 at 10:11 -0700, Darrick J. Wong wrote: > On Thu, Jun 29, 2017 at 09:19:48AM -0400, jlayton@kernel.org wrote: > > From: Jeff Layton <jlayton@redhat.com> > > > > Let's try to make this extra clear for fs authors. > > > > Cc: Jan Kara <jack@suse.cz> > > Signed-off-by: Jeff Layton <jlayton@redhat.com> > > --- > > Documentation/filesystems/vfs.txt | 43 ++++++++++++++++++++++++++++++++++++--- > > 1 file changed, 40 insertions(+), 3 deletions(-) > > > > diff --git a/Documentation/filesystems/vfs.txt b/Documentation/filesystems/vfs.txt > > index f42b90687d40..1366043b3942 100644 > > --- a/Documentation/filesystems/vfs.txt > > +++ b/Documentation/filesystems/vfs.txt > > @@ -576,7 +576,42 @@ should clear PG_Dirty and set PG_Writeback. It can be actually > > written at any point after PG_Dirty is clear. Once it is known to be > > safe, PG_Writeback is cleared. > > > > -Writeback makes use of a writeback_control structure... > > +Writeback makes use of a writeback_control structure to direct the > > +operations. This gives the the writepage and writepages operations some > > +information about the nature of and reason for the writeback request, > > +and the constraints under which it is being done. It is also used to > > +return information back to the caller about the result of a writepage or > > +writepages request. > > + > > +Handling errors during writeback > > +-------------------------------- > > +Most applications that utilize the pagecache will periodically call > > +fsync to ensure that data written has made it to the backing store. > > /me wonders if this sentence ought to be worded more strongly, e.g. > > "Applications that utilize the pagecache must call a data > synchronization syscall such as fsync, fdatasync, or msync to ensure > that data written has made it to the backing store." > Well...only if they care about the data. There are some that don't. :) > I'm also wondering -- fdatasync and msync will also report any writeback > errors that have happened anywhere (like fsync), since they all map to > vfs_fsync_range, correct? If so, I think it worth it to state > explicitly that the other *sync methods behave the same as fsync w.r.t. > writeback error reporting. > Good point. I'll fix this to make it clear that fsync, msync and fdatasync all advance the error cursor in the same way. While we're on the subject... What should we do about sync_file_range here? It doesn't currently call any filesystem operations directly, so we don't have a good way to make it selectively use errseq_t handling there. I could resurrect the FS_* flag for that, though I don't really like that. Should I just go ahead and convert it over to use errseq_t under the theory that most callers will eventually want that anyway? Thanks for the review so far! > > +When there is an error during writeback, they expect that error to be > > +reported when fsync is called. After an error has been reported on one > > +fsync, subsequent fsync calls on the same file descriptor should return > > +0, unless further writeback errors have occurred since the previous > > +fsync. > > + > > +Ideally, the kernel would report an error only on file descriptions on > > +which writes were done that subsequently failed to be written back. The > > +generic pagecache infrastructure does not track the file descriptions > > +that have dirtied each individual page however, so determining which > > +file descriptors should get back an error is not possible. > > + > > +Instead, the generic writeback error tracking infrastructure in the > > +kernel settles for reporting errors to fsync on all file descriptions > > +that were open at the time that the error occurred. In a situation with > > +multiple writers, all of them will get back an error on a subsequent fsync, > > +even if all of the writes done through that particular file descriptor > > +succeeded (or even if there were no writes on that file descriptor at all). > > + > > +Filesystems that wish to use this infrastructure should call > > +mapping_set_error to record the error in the address_space when it > > +occurs. Then, at the end of their fsync operation, they should call > > +file_check_and_advance_wb_err to ensure that the struct file's error > > +cursor has advanced to the correct point in the stream of errors emitted > > +by the backing device(s). > > > > struct address_space_operations > > ------------------------------- > > @@ -804,7 +839,8 @@ struct address_space_operations { > > The File Object > > =============== > > > > -A file object represents a file opened by a process. > > +A file object represents a file opened by a process. This is also known > > +as an "open file description" in POSIX parlance. > > > > > > struct file_operations > > @@ -887,7 +923,8 @@ otherwise noted. > > > > release: called when the last reference to an open file is closed > > > > - fsync: called by the fsync(2) system call > > + fsync: called by the fsync(2) system call. Also see the section above > > + entitled "Handling errors during writeback". > > > > fasync: called by the fcntl(2) system call when asynchronous > > (non-blocking) mode is enabled for a file > > -- > > 2.13.0 > > > > -- > > To unsubscribe from this list: send the line "unsubscribe linux-xfs" in > > the body of a message to majordomo@vger.kernel.org > > More majordomo info at http://vger.kernel.org/majordomo-info.html -- Jeff Layton <jlayton@poochiereds.net>
RnJvbTogSmVmZiBMYXl0b24gW21haWx0bzpqbGF5dG9uQHBvb2NoaWVyZWRzLm5ldF0NCj4gT24g VGh1LCAyMDE3LTA2LTI5IGF0IDEwOjExIC0wNzAwLCBEYXJyaWNrIEouIFdvbmcgd3JvdGU6DQo+ ID4gT24gVGh1LCBKdW4gMjksIDIwMTcgYXQgMDk6MTk6NDhBTSAtMDQwMCwgamxheXRvbkBrZXJu ZWwub3JnIHdyb3RlOg0KPiA+ID4gK0hhbmRsaW5nIGVycm9ycyBkdXJpbmcgd3JpdGViYWNrDQo+ ID4gPiArLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCj4gPiA+ICtNb3N0IGFwcGxp Y2F0aW9ucyB0aGF0IHV0aWxpemUgdGhlIHBhZ2VjYWNoZSB3aWxsIHBlcmlvZGljYWxseSBjYWxs DQo+ID4gPiArZnN5bmMgdG8gZW5zdXJlIHRoYXQgZGF0YSB3cml0dGVuIGhhcyBtYWRlIGl0IHRv IHRoZSBiYWNraW5nIHN0b3JlLg0KPiA+DQo+ID4gL21lIHdvbmRlcnMgaWYgdGhpcyBzZW50ZW5j ZSBvdWdodCB0byBiZSB3b3JkZWQgbW9yZSBzdHJvbmdseSwgZS5nLg0KPiA+DQo+ID4gIkFwcGxp Y2F0aW9ucyB0aGF0IHV0aWxpemUgdGhlIHBhZ2VjYWNoZSBtdXN0IGNhbGwgYSBkYXRhDQo+ID4g c3luY2hyb25pemF0aW9uIHN5c2NhbGwgc3VjaCBhcyBmc3luYywgZmRhdGFzeW5jLCBvciBtc3lu YyB0byBlbnN1cmUNCj4gPiB0aGF0IGRhdGEgd3JpdHRlbiBoYXMgbWFkZSBpdCB0byB0aGUgYmFj a2luZyBzdG9yZS4iDQo+IA0KPiBXZWxsLi4ub25seSBpZiB0aGV5IGNhcmUgYWJvdXQgdGhlIGRh dGEuIFRoZXJlIGFyZSBzb21lIHRoYXQgZG9uJ3QuIDopDQoNCkFsc28sIGFwcGxpY2F0aW9ucyBk b24ndCAidXRpbGl6ZSB0aGUgcGFnZWNhY2hlIjsgZmlsZXN5c3RlbXMgdXNlIHRoZSBwYWdlY2Fj aGUuDQpBcHBsaWNhdGlvbnMgbWF5IG9yIG1heSBub3QgdXNlIGNhY2hlZCBJL08uICBIb3cgYWJv dXQgdGhpczoNCg0KQXBwbGljYXRpb25zIHdoaWNoIGNhcmUgYWJvdXQgZGF0YSBpbnRlZ3JpdHkg YW5kIHVzZSBjYWNoZWQgSS9PIHdpbGwNCnBlcmlvZGljYWxseSBjYWxsIGZzeW5jKCksIG1zeW5j KCkgb3IgZmRhdGFzeW5jKCkgdG8gZW5zdXJlIHRoYXQgdGhlaXINCmRhdGEgaXMgZHVyYWJsZS4N Cg0KPiBXaGF0IHNob3VsZCB3ZSBkbyBhYm91dCBzeW5jX2ZpbGVfcmFuZ2UgaGVyZT8gSXQgZG9l c24ndCBjdXJyZW50bHkgY2FsbA0KPiBhbnkgZmlsZXN5c3RlbSBvcGVyYXRpb25zIGRpcmVjdGx5 LCBzbyB3ZSBkb24ndCBoYXZlIGEgZ29vZCB3YXkgdG8gbWFrZQ0KPiBpdCBzZWxlY3RpdmVseSB1 c2UgZXJyc2VxX3QgaGFuZGxpbmcgdGhlcmUuDQo+IA0KPiBJIGNvdWxkIHJlc3VycmVjdCB0aGUg RlNfKiBmbGFnIGZvciB0aGF0LCB0aG91Z2ggSSBkb24ndCByZWFsbHkgbGlrZQ0KPiB0aGF0LiBT aG91bGQgSSBqdXN0IGdvIGFoZWFkIGFuZCBjb252ZXJ0IGl0IG92ZXIgdG8gdXNlIGVycnNlcV90 IHVuZGVyDQo+IHRoZSB0aGVvcnkgdGhhdCBtb3N0IGNhbGxlcnMgd2lsbCBldmVudHVhbGx5IHdh bnQgdGhhdCBhbnl3YXk/DQoNCkkgdGhpbmsgc28uDQoNCg==
On Thu, 2017-06-29 at 07:19 -0700, Christoph Hellwig wrote:
> On Thu, Jun 29, 2017 at 09:19:39AM -0400, jlayton@kernel.org wrote:
> > From: Jeff Layton <jlayton@redhat.com>
> >
> > ext2 currently does a test+clear of the AS_EIO flag, which is
> > is problematic for some coming changes.
> >
> > What we really need to do instead is call filemap_check_errors
> > in __generic_file_fsync after syncing out the buffers. That
> > will be sufficient for this case, and help other callers detect
> > these errors properly as well.
> >
> > With that, we don't need to twiddle it in ext2.
>
> Seems like much of this code is getting replaced later in the
> series..
It does. I suppose I could squash this in with the __generic_file_fsync
patch.
--
Jeff Layton <jlayton@poochiereds.net>
On Thu, 2017-06-29 at 07:12 -0700, Christoph Hellwig wrote:
> > - if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
> > - return -EIO;
> > + if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb)))) {
> > + ret = -EIO;
> > + goto out;
> > + }
>
> This just seems to add a call to trace_ext4_sync_file_exit for this
> case, which seems unrelated to the patch.
>
> > if (ret)
> > - return ret;
> > + goto out;
> > +
>
> Same here.
>
> > /*
> > * data=writeback,ordered:
> > * The caller's filemap_fdatawrite()/wait will sync the data.
> > @@ -152,7 +155,7 @@ int ext4_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
> > needs_barrier = true;
> > ret = jbd2_complete_transaction(journal, commit_tid);
> > if (needs_barrier) {
> > - issue_flush:
> > +issue_flush:
> > err = blkdev_issue_flush(inode->i_sb->s_bdev, GFP_KERNEL, NULL);
>
> And while I much prefer your new label placement it also doesn't
> seem to belong into this patch.
I revised the patch description earlier to say:
While we're at it, ensure we always "goto out" instead of just
returning directly, so that we always hit the exit tracepoint.
...but I'm fine with taking that out if you prefer.
--
Jeff Layton <jlayton@poochiereds.net>
On Thu, 2017-06-29 at 07:17 -0700, Christoph Hellwig wrote:
> On Thu, Jun 29, 2017 at 09:19:54AM -0400, jlayton@kernel.org wrote:
> > From: Jeff Layton <jlayton@redhat.com>
> >
> > Just check and advance the errseq_t in the file before returning.
> > Internal callers of filemap_* functions are left as-is.
> >
> > Signed-off-by: Jeff Layton <jlayton@redhat.com>
> > ---
> > fs/btrfs/file.c | 7 +++++--
> > 1 file changed, 5 insertions(+), 2 deletions(-)
> >
> > diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c
> > index da1096eb1a40..1f57e1a523d9 100644
> > --- a/fs/btrfs/file.c
> > +++ b/fs/btrfs/file.c
> > @@ -2011,7 +2011,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
> > struct btrfs_root *root = BTRFS_I(inode)->root;
> > struct btrfs_trans_handle *trans;
> > struct btrfs_log_ctx ctx;
> > - int ret = 0;
> > + int ret = 0, err;
> > bool full_sync = 0;
> > u64 len;
> >
> > @@ -2030,7 +2030,7 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
> > */
> > ret = start_ordered_ops(inode, start, end);
> > if (ret)
> > - return ret;
> > + goto out;
> >
> > inode_lock(inode);
> > atomic_inc(&root->log_batch);
> > @@ -2227,6 +2227,9 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
> > ret = btrfs_end_transaction(trans);
> > }
> > out:
> > + err = file_check_and_advance_wb_err(file);
> > + if (!ret)
> > + ret = err;
> > return ret > 0 ? -EIO : ret;
>
> This means that we'll lose the exact error returned from
> start_ordered_ops. Beyond that I can't really provide good feedback
> as the btrfs fsync code looks so much different from all the other
> fs fsync code..
Well, no...we'll keep the error from start_ordered_ops if there was one.
We just advance the cursor past any stored error in that case without
returning it.
I have another fix for this patch too: there's a call to
filemap_check_errors in this function that I think should probably use
filemap_check_wb_err instead. Fixed in my tree.
I do agree though that while this works in my testing I'd like the btrfs
guys to ACK this as I don't fully grok the btrfs fsync code at all.
--
Jeff Layton <jlayton@poochiereds.net>
On Thu, 2017-06-29 at 18:21 +0000, Matthew Wilcox wrote: > From: Jeff Layton [mailto:jlayton@poochiereds.net] > > On Thu, 2017-06-29 at 10:11 -0700, Darrick J. Wong wrote: > > > On Thu, Jun 29, 2017 at 09:19:48AM -0400, jlayton@kernel.org wrote: > > > > +Handling errors during writeback > > > > +-------------------------------- > > > > +Most applications that utilize the pagecache will periodically call > > > > +fsync to ensure that data written has made it to the backing store. > > > > > > /me wonders if this sentence ought to be worded more strongly, e.g. > > > > > > "Applications that utilize the pagecache must call a data > > > synchronization syscall such as fsync, fdatasync, or msync to ensure > > > that data written has made it to the backing store." > > > > Well...only if they care about the data. There are some that don't. :) > > Also, applications don't "utilize the pagecache"; filesystems use the pagecache. > Applications may or may not use cached I/O. How about this: > I meant "applications that do buffered I/O" as opposed to O_DIRECT, but yeah that's not very clear. > Applications which care about data integrity and use cached I/O will > periodically call fsync(), msync() or fdatasync() to ensure that their > data is durable. > > > What should we do about sync_file_range here? It doesn't currently call > > any filesystem operations directly, so we don't have a good way to make > > it selectively use errseq_t handling there. > > > > I could resurrect the FS_* flag for that, though I don't really like > > that. Should I just go ahead and convert it over to use errseq_t under > > the theory that most callers will eventually want that anyway? > > I think so. Ok, I'll leave that for the next pile of patches though. Here's a revised section ------------------------------8<-------------------------------- Handling errors during writeback -------------------------------- Most applications that do buffered I/O will periodically call a file synchronization call (fsync, fdatasync, msync or sync_file_range) to ensure that data written has made it to the backing store. When there is an error during writeback, they expect that error to be reported when a file sync request is made. After an error has been reported on one request, subsequent requests on the same file descriptor should return 0, unless further writeback errors have occurred since the previous file syncronization. Ideally, the kernel would report errors only on file descriptions on which writes were done that subsequently failed to be written back. The generic pagecache infrastructure does not track the file descriptions that have dirtied each individual page however, so determining which file descriptors should get back an error is not possible. Instead, the generic writeback error tracking infrastructure in the kernel settles for reporting errors to fsync on all file descriptions that were open at the time that the error occurred. In a situation with multiple writers, all of them will get back an error on a subsequent fsync, even if all of the writes done through that particular file descriptor succeeded (or even if there were no writes on that file descriptor at all). Filesystems that wish to use this infrastructure should call mapping_set_error to record the error in the address_space when it occurs. Then, after writing back data from the pagecache in their file->fsync operation, they should call file_check_and_advance_wb_err to ensure that the struct file's error cursor has advanced to the correct point in the stream of errors emitted by the backing device(s). ------------------------------8<-------------------------------- Thanks for the review so far! -- Jeff Layton <jlayton@poochiereds.net>
On Thu, 2017-06-29 at 07:12 -0700, Christoph Hellwig wrote:
> Nice and simple, this looks great!
>
> Reviewed-by: Christoph Hellwig <hch@lst.de>
Thanks! I think this turned out to be a lot cleaner too.
For filesystems that use filemap_write_and_wait_range today this now
becomes a pretty straight conversion to file_write_and_wait_range -- one
liner patches for the most part.
I've started rolling patches to do that, but now I'm wondering...
Should I aim to do that with an individual patch for each fs, or is it
better to do a swath of them all at once in a single patch here?
--
Jeff Layton <jlayton@redhat.com>
On Fri, Jun 30, 2017 at 12:45:54PM -0400, Jeff Layton wrote:
> Should I aim to do that with an individual patch for each fs, or is it
> better to do a swath of them all at once in a single patch here?
I'd be perfectly happy with one big patch for all the trivial
conversions.