Skip to content

Commit

Permalink
Merge tag 'for-linus-v4.13-2' of git://git.kernel.org/pub/scm/linux/k…
Browse files Browse the repository at this point in the history
…ernel/git/jlayton/linux

Pull Writeback error handling updates from Jeff Layton:
 "This pile represents the bulk of the writeback error handling fixes
  that I have for this cycle. Some of the earlier patches in this pile
  may look trivial but they are prerequisites for later patches in the
  series.

  The aim of this set is to improve how we track and report writeback
  errors to userland. Most applications that care about data integrity
  will periodically call fsync/fdatasync/msync to ensure that their
  writes have made it to the backing store.

  For a very long time, we have tracked writeback errors using two flags
  in the address_space: AS_EIO and AS_ENOSPC. Those flags are set when a
  writeback error occurs (via mapping_set_error) and are cleared as a
  side-effect of filemap_check_errors (as you noted yesterday). This
  model really sucks for userland.

  Only the first task to call fsync (or msync or fdatasync) will see the
  error. Any subsequent task calling fsync on a file will get back 0
  (unless another writeback error occurs in the interim). If I have
  several tasks writing to a file and calling fsync to ensure that their
  writes got stored, then I need to have them coordinate with one
  another. That's difficult enough, but in a world of containerized
  setups that coordination may even not be possible.

  But wait...it gets worse!

  The calls to filemap_check_errors can be buried pretty far down in the
  call stack, and there are internal callers of filemap_write_and_wait
  and the like that also end up clearing those errors. Many of those
  callers ignore the error return from that function or return it to
  userland at nonsensical times (e.g. truncate() or stat()). If I get
  back -EIO on a truncate, there is no reason to think that it was
  because some previous writeback failed, and a subsequent fsync() will
  (incorrectly) return 0.

  This pile aims to do three things:

   1) ensure that when a writeback error occurs that that error will be
      reported to userland on a subsequent fsync/fdatasync/msync call,
      regardless of what internal callers are doing

   2) report writeback errors on all file descriptions that were open at
      the time that the error occurred. This is a user-visible change,
      but I think most applications are written to assume this behavior
      anyway. Those that aren't are unlikely to be hurt by it.

   3) document what filesystems should do when there is a writeback
      error. Today, there is very little consistency between them, and a
      lot of cargo-cult copying. We need to make it very clear what
      filesystems should do in this situation.

  To achieve this, the set adds a new data type (errseq_t) and then
  builds new writeback error tracking infrastructure around that. Once
  all of that is in place, we change the filesystems to use the new
  infrastructure for reporting wb errors to userland.

  Note that this is just the initial foray into cleaning up this mess.
  There is a lot of work remaining here:

   1) convert the rest of the filesystems in a similar fashion. Once the
      initial set is in, then I think most other fs' will be fairly
      simple to convert. Hopefully most of those can in via individual
      filesystem trees.

   2) convert internal waiters on writeback to use errseq_t for
      detecting errors instead of relying on the AS_* flags. I have some
      draft patches for this for ext4, but they are not quite ready for
      prime time yet.

  This was a discussion topic this year at LSF/MM too. If you're
  interested in the gory details, LWN has some good articles about this:

      https://lwn.net/Articles/718734/
      https://lwn.net/Articles/724307/"

* tag 'for-linus-v4.13-2' of git://git.kernel.org/pub/scm/linux/kernel/git/jlayton/linux:
  btrfs: minimal conversion to errseq_t writeback error reporting on fsync
  xfs: minimal conversion to errseq_t writeback error reporting
  ext4: use errseq_t based error handling for reporting data writeback errors
  fs: convert __generic_file_fsync to use errseq_t based reporting
  block: convert to errseq_t based writeback error tracking
  dax: set errors in mapping when writeback fails
  Documentation: flesh out the section in vfs.txt on storing and reporting writeback errors
  mm: set both AS_EIO/AS_ENOSPC and errseq_t in mapping_set_error
  fs: new infrastructure for writeback error handling and reporting
  lib: add errseq_t type and infrastructure for handling it
  mm: don't TestClearPageError in __filemap_fdatawait_range
  mm: clear AS_EIO/AS_ENOSPC when writeback initiation fails
  jbd2: don't clear and reset errors after waiting on writeback
  buffer: set errors in mapping at the time that the error occurs
  fs: check for writeback errors after syncing out buffers in generic_file_fsync
  buffer: use mapping_set_error instead of setting the flag
  mm: fix mapping_set_error call in me_pagecache_dirty
  • Loading branch information
torvalds committed Jul 8, 2017
2 parents 33198c1 + 333427a commit 088737f
Show file tree
Hide file tree
Showing 24 changed files with 572 additions and 63 deletions.
44 changes: 41 additions & 3 deletions Documentation/filesystems/vfs.txt
Expand Up @@ -576,7 +576,43 @@ 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 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).

struct address_space_operations
-------------------------------
Expand Down Expand Up @@ -804,7 +840,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
Expand Down Expand Up @@ -887,7 +924,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
Expand Down
6 changes: 6 additions & 0 deletions MAINTAINERS
Expand Up @@ -5069,6 +5069,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
Expand Down
1 change: 1 addition & 0 deletions drivers/dax/device.c
Expand Up @@ -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;

Expand Down
3 changes: 2 additions & 1 deletion fs/block_dev.c
Expand Up @@ -632,7 +632,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;

Expand Down Expand Up @@ -1751,6 +1751,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);
}
Expand Down
13 changes: 8 additions & 5 deletions fs/btrfs/file.c
Expand Up @@ -2032,7 +2032,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;

Expand All @@ -2051,7 +2051,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);
Expand Down Expand Up @@ -2156,10 +2156,10 @@ int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
* An ordered extent might have started before and completed
* already with io errors, in which case the inode was not
* updated and we end up here. So check the inode's mapping
* flags for any errors that might have happened while doing
* writeback of file data.
* for any errors that might have happened since we last
* checked called fsync.
*/
ret = filemap_check_errors(inode->i_mapping);
ret = filemap_check_wb_err(inode->i_mapping, file->f_wb_err);
inode_unlock(inode);
goto out;
}
Expand Down Expand Up @@ -2248,6 +2248,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;
}

Expand Down
20 changes: 13 additions & 7 deletions fs/buffer.c
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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))
set_bit(AS_EIO, &bh->b_assoc_map->flags);
bh->b_assoc_map = NULL;
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -3282,8 +3290,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;
Expand Down
4 changes: 3 additions & 1 deletion fs/dax.c
Expand Up @@ -855,8 +855,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;
}
}
start_index = indices[pvec.nr - 1] + 1;
}
Expand Down
5 changes: 1 addition & 4 deletions fs/ext2/file.c
Expand Up @@ -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;
}

Expand Down
2 changes: 1 addition & 1 deletion fs/ext4/fsync.c
Expand Up @@ -124,7 +124,7 @@ 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;
/*
Expand Down
1 change: 1 addition & 0 deletions fs/file_table.c
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion fs/gfs2/lops.c
Expand Up @@ -180,7 +180,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;
Expand Down
16 changes: 4 additions & 12 deletions fs/jbd2/commit.c
Expand Up @@ -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();
Expand Down
6 changes: 5 additions & 1 deletion fs/libfs.c
Expand Up @@ -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;

Expand All @@ -991,6 +991,10 @@ int __generic_file_fsync(struct file *file, loff_t start, loff_t end,

out:
inode_unlock(inode);
/* 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);
Expand Down
3 changes: 3 additions & 0 deletions fs/open.c
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion fs/xfs/xfs_file.c
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions include/linux/buffer_head.h
Expand Up @@ -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,
Expand Down
19 changes: 19 additions & 0 deletions 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

0 comments on commit 088737f

Please sign in to comment.