• Filipe Manana's avatar
    btrfs: ensure fast fsync waits for ordered extents after a write failure · f13e01b8
    Filipe Manana authored
    If a write path in COW mode fails, either before submitting a bio for the
    new extents or an actual IO error happens, we can end up allowing a fast
    fsync to log file extent items that point to unwritten extents.
    
    This is because dropping the extent maps happens when completing ordered
    extents, at btrfs_finish_one_ordered(), and the completion of an ordered
    extent is executed in a work queue.
    
    This can result in a fast fsync to start logging file extent items based
    on existing extent maps before the ordered extents complete, therefore
    resulting in a log that has file extent items that point to unwritten
    extents, resulting in a corrupt file if a crash happens after and the log
    tree is replayed the next time the fs is mounted.
    
    This can happen for both direct IO writes and buffered writes.
    
    For example consider a direct IO write, in COW mode, that fails at
    btrfs_dio_submit_io() because btrfs_extract_ordered_extent() returned an
    error:
    
    1) We call btrfs_finish_ordered_extent() with the 'uptodate' parameter
       set to false, meaning an error happened;
    
    2) That results in marking the ordered extent with the BTRFS_ORDERED_IOERR
       flag;
    
    3) btrfs_finish_ordered_extent() queues the completion of the ordered
       extent - so that btrfs_finish_one_ordered() will be executed later in
       a work queue. That function will drop extent maps in the range when
       it's executed, since the extent maps point to unwritten locations
       (signaled by the BTRFS_ORDERED_IOERR flag);
    
    4) After calling btrfs_finish_ordered_extent() we keep going down the
       write path and unlock the inode;
    
    5) After that a fast fsync starts and locks the inode;
    
    6) Before the work queue executes btrfs_finish_one_ordered(), the fsync
       task sees the extent maps that point to the unwritten locations and
       logs file extent items based on them - it does not know they are
       unwritten, and the fast fsync path does not wait for ordered extents
       to complete, which is an intentional behaviour in order to reduce
       latency.
    
    For the buffered write case, here's one example:
    
    1) A fast fsync begins, and it starts by flushing delalloc and waiting for
       the writeback to complete by calling filemap_fdatawait_range();
    
    2) Flushing the dellaloc created a new extent map X;
    
    3) During the writeback some IO error happened, and at the end io callback
       (end_bbio_data_write()) we call btrfs_finish_ordered_extent(), which
       sets the BTRFS_ORDERED_IOERR flag in the ordered extent and queues its
       completion;
    
    4) After queuing the ordered extent completion, the end io callback clears
       the writeback flag from all pages (or folios), and from that moment the
       fast fsync can proceed;
    
    5) The fast fsync proceeds sees extent map X and logs a file extent item
       based on extent map X, resulting in a log that points to an unwritten
       data extent - because the ordered extent completion hasn't run yet, it
       happens only after the logging.
    
    To fix this make btrfs_finish_ordered_extent() set the inode flag
    BTRFS_INODE_NEEDS_FULL_SYNC in case an error happened for a COW write,
    so that a fast fsync will wait for ordered extent completion.
    
    Note that this issues of using extent maps that point to unwritten
    locations can not happen for reads, because in read paths we start by
    locking the extent range and wait for any ordered extents in the range
    to complete before looking for extent maps.
    Reviewed-by: default avatarQu Wenruo <wqu@suse.com>
    Signed-off-by: default avatarFilipe Manana <fdmanana@suse.com>
    Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
    f13e01b8
file.c 115 KB