diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 0bb3257cba42998eab6b8ad76b9bd1d3cc7340c0..95e787f9e694ea29c2e15949ff20f14e564d6034 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -1409,6 +1409,7 @@ iomap_add_to_ioend(struct inode *inode, loff_t offset, struct page *page, unsigned len = i_blocksize(inode); unsigned poff = offset & (PAGE_SIZE - 1); bool merged, same_page = false; + loff_t isize = i_size_read(inode); if (!wpc->ioend || !iomap_can_add_to_ioend(wpc, offset, sector)) { if (wpc->ioend) @@ -1429,7 +1430,53 @@ iomap_add_to_ioend(struct inode *inode, loff_t offset, struct page *page, bio_add_page(wpc->ioend->io_bio, page, len, poff); } + /* + * Clamp io_offset and io_size to the incore EOF so that ondisk + * file size updates in the ioend completion are byte-accurate. + * This avoids recovering files with zeroed tail regions when + * writeback races with appending writes: + * + * Thread 1: Thread 2: + * ------------ ----------- + * write [A, A+B] + * update inode size to A+B + * submit I/O [A, A+BS] + * write [A+B, A+B+C] + * update inode size to A+B+C + * + * + * + * After reboot: + * 1) with A+B+C < A+BS, the file has zero padding in range + * [A+B, A+B+C] + * + * |< Block Size (BS) >| + * |DDDDDDDDDDDD0000000000000| + * ^ ^ ^ + * A A+B A+B+C + * (EOF) + * + * 2) with A+B+C > A+BS, the file has zero padding in range + * [A+B, A+BS] + * + * |< Block Size (BS) >|< Block Size (BS) >| + * |DDDDDDDDDDDD0000000000000|00000000000000000000000000| + * ^ ^ ^ ^ + * A A+B A+BS A+B+C + * (EOF) + * + * D = Valid Data + * 0 = Zero Padding + * + * Note that this defeats the ability to chain the ioends of + * appending writes. Writeback beyond EOF block may occur in + * concurrent scenarios(e.g. racing with truncate) and io_size + * should not be trimmed in such cases. + */ wpc->ioend->io_size += len; + if (offset < isize && offset + len > isize) + wpc->ioend->io_size = isize - wpc->ioend->io_offset; + wbc_account_cgroup_owner(wbc, page, len); } diff --git a/include/linux/iomap.h b/include/linux/iomap.h index 1b6e22741d43002e8ecbad1bc980da64101eb858..ff3473c134b37a19fecb2f7589349e82a89d2f58 100644 --- a/include/linux/iomap.h +++ b/include/linux/iomap.h @@ -229,7 +229,7 @@ struct iomap_ioend { u16 io_flags; /* IOMAP_F_* */ u32 io_folios; /* folios added to ioend */ struct inode *io_inode; /* file being written to */ - size_t io_size; /* size of the extent */ + size_t io_size; /* size of data within eof */ loff_t io_offset; /* offset in the file */ void *io_private; /* file system private data */ sector_t io_sector; /* start sector of ioend */