summaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
Diffstat (limited to 'fs')
-rw-r--r--fs/attr.c2
-rw-r--r--fs/btrfs/Kconfig3
-rw-r--r--fs/btrfs/btrfs_inode.h1
-rw-r--r--fs/btrfs/disk-io.c38
-rw-r--r--fs/btrfs/extent_io.c113
-rw-r--r--fs/btrfs/fs.c12
-rw-r--r--fs/btrfs/fs.h12
-rw-r--r--fs/btrfs/inode.c221
-rw-r--r--fs/btrfs/lzo.c5
-rw-r--r--fs/btrfs/subpage.c216
-rw-r--r--fs/btrfs/subpage.h41
-rw-r--r--fs/crypto/policy.c2
-rw-r--r--fs/nfs/nfs4proc.c27
-rw-r--r--fs/smb/client/cifsfs.c2
-rw-r--r--fs/smb/client/sess.c2
-rw-r--r--fs/smb/client/smb1transport.c28
-rw-r--r--fs/smb/common/compress/compress.c11
-rw-r--r--fs/smb/common/compress/compress.h3
-rw-r--r--fs/smb/server/compress.c14
-rw-r--r--fs/smb/server/connection.c6
-rw-r--r--fs/smb/server/connection.h9
-rw-r--r--fs/verity/measure.c15
-rw-r--r--fs/xfs/libxfs/xfs_exchmaps.c10
-rw-r--r--fs/xfs/libxfs/xfs_rtrefcount_btree.c4
-rw-r--r--fs/xfs/libxfs/xfs_sb.c4
-rw-r--r--fs/xfs/scrub/agheader.c98
-rw-r--r--fs/xfs/scrub/agheader_repair.c169
-rw-r--r--fs/xfs/scrub/alloc.c5
-rw-r--r--fs/xfs/scrub/attr_repair.c3
-rw-r--r--fs/xfs/scrub/bmap.c5
-rw-r--r--fs/xfs/scrub/dirtree_repair.c22
-rw-r--r--fs/xfs/scrub/inode_repair.c6
-rw-r--r--fs/xfs/scrub/nlinks.c13
-rw-r--r--fs/xfs/scrub/nlinks_repair.c7
-rw-r--r--fs/xfs/scrub/parent.c2
-rw-r--r--fs/xfs/scrub/rtbitmap_repair.c46
-rw-r--r--fs/xfs/scrub/rtsummary.c2
-rw-r--r--fs/xfs/scrub/tempfile.c1
-rw-r--r--fs/xfs/scrub/trace.h2
-rw-r--r--fs/xfs/xfs_buf.c2
-rw-r--r--fs/xfs/xfs_buf_item_recover.c57
-rw-r--r--fs/xfs/xfs_dquot.c3
-rw-r--r--fs/xfs/xfs_dquot_item_recover.c2
-rw-r--r--fs/xfs/xfs_mount.h7
-rw-r--r--fs/xfs/xfs_rtalloc.c2
-rw-r--r--fs/xfs/xfs_super.c53
-rw-r--r--fs/xfs/xfs_zone_gc.c13
47 files changed, 1153 insertions, 168 deletions
diff --git a/fs/attr.c b/fs/attr.c
index 4f437fabb7f0..71888ac903c2 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -176,7 +176,7 @@ int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry,
* covered by the open-time check because sys_truncate() takes a
* path, not an open file.
*/
- if (IS_ENABLED(CONFIG_FS_VERITY) && IS_VERITY(inode))
+ if (IS_VERITY(inode))
return -EPERM;
error = inode_newsize_ok(inode, attr->ia_size);
diff --git a/fs/btrfs/Kconfig b/fs/btrfs/Kconfig
index 9de04c37e11a..4b10d78ed99b 100644
--- a/fs/btrfs/Kconfig
+++ b/fs/btrfs/Kconfig
@@ -106,7 +106,8 @@ config BTRFS_EXPERIMENTAL
- extent tree v2 - complex rework of extent tracking
- - block size > page size support
+ - block size > page size support - needs transparent huge page and
+ non-HIGHMEM system
- huge folios for data - folios can be as large as 2MiB now
diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
index 7fdc6c3fd066..1082fa92c145 100644
--- a/fs/btrfs/btrfs_inode.h
+++ b/fs/btrfs/btrfs_inode.h
@@ -600,6 +600,7 @@ int btrfs_prealloc_file_range_trans(struct inode *inode,
loff_t actual_len, u64 *alloc_hint);
int btrfs_run_delalloc_range(struct btrfs_inode *inode, struct folio *locked_folio,
u64 start, u64 end, struct writeback_control *wbc);
+void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio);
int btrfs_encoded_io_compression_from_extent(struct btrfs_fs_info *fs_info,
int compress_type);
int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode,
diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 22c321719e4f..2f1666d9544e 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -1760,6 +1760,8 @@ static int read_backup_root(struct btrfs_fs_info *fs_info, u8 priority)
/* helper to cleanup workers */
static void btrfs_stop_all_workers(struct btrfs_fs_info *fs_info)
{
+ if (fs_info->fixup_workers)
+ destroy_workqueue(fs_info->fixup_workers);
btrfs_destroy_workqueue(fs_info->delalloc_workers);
btrfs_destroy_workqueue(fs_info->workers);
if (fs_info->endio_workers)
@@ -1967,6 +1969,9 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info)
fs_info->caching_workers =
btrfs_alloc_workqueue(fs_info, "cache", flags, max_active, 0);
+ fs_info->fixup_workers =
+ alloc_ordered_workqueue("btrfs-fixup", ordered_flags);
+
fs_info->endio_workers =
alloc_workqueue("btrfs-endio", flags, max_active);
fs_info->endio_meta_workers =
@@ -1992,7 +1997,7 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info)
fs_info->endio_workers && fs_info->endio_meta_workers &&
fs_info->endio_write_workers &&
fs_info->endio_freespace_worker && fs_info->rmw_workers &&
- fs_info->caching_workers &&
+ fs_info->caching_workers && fs_info->fixup_workers &&
fs_info->delayed_workers && fs_info->qgroup_rescan_workers &&
fs_info->discard_ctl.discard_workers)) {
return -ENOMEM;
@@ -3468,7 +3473,15 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device
fs_info->sectorsize = sectorsize;
fs_info->sectorsize_bits = ilog2(sectorsize);
fs_info->block_min_order = ilog2(round_up(sectorsize, PAGE_SIZE) >> PAGE_SHIFT);
- fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits);
+ /*
+ * For HIGHMEM, a large folio cannot be mapped in one go, breaking a lot
+ * of basic assumptions for btrfs IOs.
+ * Disable large folios for such 32-bit systems.
+ */
+ if (IS_ENABLED(CONFIG_HIGHMEM))
+ fs_info->block_max_order = fs_info->block_min_order;
+ else
+ fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits);
fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) / fs_info->csum_size;
fs_info->stripesize = stripesize;
fs_info->fs_devices->fs_info = fs_info;
@@ -4357,6 +4370,18 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info)
btrfs_cleanup_defrag_inodes(fs_info);
/*
+ * Before the unmount, we sync down all the writeback which can
+ * generate fixup work. We are about to run delalloc for autodefrag so
+ * piggy back on that by also flushing the fixup work which can also
+ * generate delalloc we would like to get run.
+ *
+ * After this, it is still possible that some thread doing writeback is
+ * in btrfs_queue_writepage_fixup() and might finish queueing some final
+ * work, racing the btrfs_fs_closing() check there.
+ */
+ flush_workqueue(fs_info->fixup_workers);
+
+ /*
* Handle the error fs first, as it will flush and wait for all ordered
* extents. This will generate delayed iputs, thus we want to handle
* it first.
@@ -4434,6 +4459,15 @@ void __cold close_ctree(struct btrfs_fs_info *fs_info)
cancel_work_sync(&fs_info->em_shrinker_work);
/*
+ * Reclaim workers can run writeback which can queue fixup.
+ * After the above cancel_work_sync() calls, any such queueing attempts are
+ * guaranteed to see btrfs_fs_closing(), so at this point we can genuinely fully
+ * flush the fixup workqueue. This relies on the belief that *now* no thread can
+ * still be sitting in btrfs_queue_writepage_fixup().
+ */
+ flush_workqueue(fs_info->fixup_workers);
+
+ /*
* Run delayed iputs again because an async reclaim worker may have
* added new ones if it was flushing delalloc:
*
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index de5785117a47..f032f0858f40 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -1441,6 +1441,115 @@ static bool find_next_delalloc_bitmap(struct folio *folio,
}
/*
+ * Debug checks for fixup selection logic to help ensure the invariants
+ * we expect for fixup marking hold in practice.
+ *
+ * - A dirty block without a fixup bit is covered by delalloc or a running
+ * ordered extent (it was dirtied by a reserving write path).
+ * - A block with a fixup bit is never covered by delalloc: every delalloc
+ * setter holds the folio lock and cancels the fixup state of the blocks
+ * it covers (btrfs_folio_set_dirty()) before releasing it.
+ */
+static void debug_check_writepage_fixup(struct btrfs_inode *inode, u64 start,
+ u32 len, bool needs_fixup)
+{
+ struct btrfs_ordered_extent *ordered;
+ bool delalloc;
+
+ if (!IS_ENABLED(CONFIG_BTRFS_DEBUG))
+ return;
+
+ delalloc = btrfs_test_range_bit_exists(&inode->io_tree, start,
+ start + len - 1, EXTENT_DELALLOC);
+ if (needs_fixup) {
+ if (unlikely(delalloc))
+ DEBUG_WARN("writeback: delalloc and fixup conflict. ino %llu start %llu",
+ btrfs_ino(inode), start);
+ } else {
+ if (delalloc)
+ return;
+
+ ordered = btrfs_lookup_ordered_range(inode, start, len);
+ if (unlikely(!ordered))
+ DEBUG_WARN("dirty block, no delalloc, fixup, ordered. ino %llu start %llu",
+ btrfs_ino(inode), start);
+ else
+ btrfs_put_ordered_extent(ordered);
+ }
+}
+
+/*
+ * Handle folios dirtied without a delalloc reservation, e.g.
+ * O_DIRECT read into a MAP_SHARED mapping dirtying via set_page_dirty_lock().
+ *
+ * btrfs_data_dirty_folio() records the affected blocks in the fixup bitmap
+ * and the folio fixup flag and we check them here in writeback.
+ *
+ * Don't submit such blocks and queue work for the fixup worker to reserve
+ * space for them so that they can be submitted properly by writeback.
+ *
+ * Return 1 if the folio needed fixup, 0 if not, and a negative error code
+ * on error.
+ */
+static noinline_for_stack int writepage_fixup(struct btrfs_inode *inode,
+ struct folio *folio,
+ struct btrfs_bio_ctrl *bio_ctrl)
+{
+ struct btrfs_fs_info *fs_info = inode_to_fs_info(&inode->vfs_inode);
+ const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio);
+ const u32 sectorsize = fs_info->sectorsize;
+ const u64 page_start = folio_pos(folio);
+ bool found_fixup = false;
+ unsigned int bit;
+
+ /*
+ * A folio was dirtied without calling aops->dirty_folio() which we
+ * explicitly assert is not allowed.
+ */
+ if (unlikely(bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio))) {
+ DEBUG_WARN();
+ btrfs_err_rl(fs_info,
+ "root %lld ino %llu folio %llu is dirty with an empty dirty bitmap",
+ btrfs_root_id(inode->root), btrfs_ino(inode),
+ folio_pos(folio));
+ return -EUCLEAN;
+ }
+
+ /* Cheap check on the folio flag. Set iff the fixup bitmap is non-empty. */
+ if (likely(!folio_test_fixup_pending(folio)))
+ return 0;
+
+ for_each_set_bit(bit, bio_ctrl->submit_bitmap, blocks_per_folio) {
+ const u64 start = page_start + (bit << fs_info->sectorsize_bits);
+ const bool needs_fixup = btrfs_folio_test_fixup(fs_info, folio,
+ start, sectorsize);
+
+ debug_check_writepage_fixup(inode, start, sectorsize, needs_fixup);
+ if (needs_fixup) {
+ bitmap_clear(bio_ctrl->submit_bitmap, bit, 1);
+ found_fixup = true;
+ }
+ }
+ if (likely(found_fixup)) {
+ btrfs_queue_writepage_fixup(inode, folio);
+ folio_redirty_for_writepage(bio_ctrl->wbc, folio);
+ if (bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio)) {
+ folio_unlock(folio);
+ return 1;
+ }
+ return 0;
+ }
+ /* We should always find fixup if the folio fixup flag was set. */
+ DEBUG_WARN();
+ btrfs_err_rl(fs_info,
+ "root %lld ino %llu folio %llu is fixup with an empty fixup bitmap",
+ btrfs_root_id(inode->root), btrfs_ino(inode),
+ folio_pos(folio));
+
+ return -EUCLEAN;
+}
+
+/*
* Do all of the delayed allocation setup.
*
* Return >0 if all the dirty blocks are submitted async (compression) or inlined.
@@ -1492,6 +1601,10 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode,
/* Save the dirty bitmap as our submission bitmap will be a subset of it. */
btrfs_copy_subpage_dirty_bitmap(fs_info, folio, bio_ctrl->submit_bitmap);
+ ret = writepage_fixup(inode, folio, bio_ctrl);
+ if (ret)
+ return ret;
+
for_each_set_bitrange(start_bit, end_bit, bio_ctrl->submit_bitmap,
blocks_per_folio) {
u64 start = page_start + (start_bit << fs_info->sectorsize_bits);
diff --git a/fs/btrfs/fs.c b/fs/btrfs/fs.c
index 14d83565cdee..dcf12979af33 100644
--- a/fs/btrfs/fs.c
+++ b/fs/btrfs/fs.c
@@ -166,9 +166,17 @@ bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize)
*
* Considering HIGHMEM is such a pain to deal with and it's going
* to be deprecated eventually, just reject HIGHMEM && bs > ps cases.
+ *
+ * Finally, for bs > ps cases, we need to set the minimal folio order,
+ * which requires transparent hugepage.
*/
- if (IS_ENABLED(CONFIG_HIGHMEM) && blocksize > PAGE_SIZE)
- return false;
+ if (blocksize > PAGE_SIZE) {
+ if (IS_ENABLED(CONFIG_HIGHMEM))
+ return false;
+
+ if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
+ return false;
+ }
return true;
#endif
return false;
diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h
index 7ee9ec2b0efb..f7f343fbe732 100644
--- a/fs/btrfs/fs.h
+++ b/fs/btrfs/fs.h
@@ -713,6 +713,8 @@ struct btrfs_fs_info {
struct btrfs_workqueue *endio_write_workers;
struct btrfs_workqueue *endio_freespace_worker;
struct btrfs_workqueue *caching_workers;
+
+ struct workqueue_struct *fixup_workers;
struct btrfs_workqueue *delayed_workers;
struct task_struct *transaction_kthread;
@@ -1200,6 +1202,16 @@ static inline void btrfs_wake_unfinished_drop(struct btrfs_fs_info *fs_info)
clear_and_wake_up_bit(BTRFS_FS_UNFINISHED_DROPS, &fs_info->flags);
}
+/*
+ * We use the folio owner_2 flag to indicate the folio has blocks that were
+ * dirtied without a space reservation and need the writepage fixup before
+ * writeback. For bs < folio_size the fixup bitmap tracks the affected
+ * blocks.
+ */
+#define folio_test_fixup_pending(folio) folio_test_owner_2(folio)
+#define folio_set_fixup_pending(folio) folio_set_owner_2(folio)
+#define folio_clear_fixup_pending(folio) folio_clear_owner_2(folio)
+
#define BTRFS_FS_ERROR(fs_info) (READ_ONCE((fs_info)->fs_error))
#define BTRFS_FS_LOG_CLEANUP_ERROR(fs_info) \
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index b446c3014b24..2534cd9284d5 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -2812,6 +2812,180 @@ int btrfs_set_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end,
EXTENT_DELALLOC | extra_bits, cached_state);
}
+struct btrfs_writepage_fixup {
+ struct folio *folio;
+ struct btrfs_inode *inode;
+ struct work_struct work;
+};
+
+/*
+ * Do the real fixup work of reserving space for the blocks a folio's fixup
+ * state records. Queued by writepage_fixup() when writeback found the bits set.
+ *
+ * Since the fixup can be cancelled by a task dirtying with a reservation, we must
+ * re-check the state of fixup under the folio lock.
+ */
+static void btrfs_writepage_fixup_worker(struct work_struct *work)
+{
+ struct btrfs_writepage_fixup *fixup =
+ container_of(work, struct btrfs_writepage_fixup, work);
+ struct extent_state *cached_state = NULL;
+ struct extent_changeset *data_reserved = NULL;
+ unsigned long delalloc_bitmap[BITS_TO_LONGS(BTRFS_MAX_BLOCKS_PER_FOLIO)] = { 0 };
+ struct folio *folio = fixup->folio;
+ struct btrfs_inode *inode = fixup->inode;
+ struct btrfs_fs_info *fs_info = inode->root->fs_info;
+ const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio);
+ const u32 sectorsize = fs_info->sectorsize;
+ const u64 page_start = folio_pos(folio);
+ const u64 page_end = folio_next_pos(folio) - 1;
+ unsigned int start_bit;
+ unsigned int end_bit;
+ unsigned int bit;
+ bool reserved;
+ int ret;
+
+ /*
+ * We would prefer to reserve under the folio lock when we know exactly
+ * which blocks need a reservation. Unfortunately, since the reservation
+ * can go into flushers which can go into writeback, which takes folio
+ * locks, that is not possible. Therefore, we have to reserve for the
+ * whole folio here, then release what we didn't end up needing once we
+ * figure it out.
+ *
+ * Also note the slightly strange error checking. If fixup is actually
+ * not set, we don't need to mark an error on the mapping. So hang on to
+ * ret until after we lock and find out if we actually care.
+ */
+ ret = btrfs_delalloc_reserve_space(inode, &data_reserved, page_start,
+ folio_size(folio));
+ reserved = (ret == 0);
+again:
+ folio_lock(folio);
+
+ if (!folio->mapping || !folio_test_fixup_pending(folio)) {
+ ret = 0;
+ goto out;
+ }
+ if (ret)
+ goto out;
+
+ btrfs_lock_extent(&inode->io_tree, page_start, page_end, &cached_state);
+
+ for (bit = 0; bit < blocks_per_folio; bit++) {
+ struct btrfs_ordered_extent *ordered;
+ const u64 start = page_start + (bit << fs_info->sectorsize_bits);
+
+ if (test_bit(bit, delalloc_bitmap))
+ continue;
+ if (!btrfs_folio_test_fixup(fs_info, folio, start, sectorsize))
+ continue;
+ /*
+ * Any task that sets EXTENT_DELALLOC clears the fixup bits
+ * under the folio lock, so it should be impossible to observe
+ * both under the lock. Setting delalloc twice would wrongly
+ * double account the space.
+ */
+ if (IS_ENABLED(CONFIG_BTRFS_DEBUG) &&
+ unlikely(btrfs_test_range_bit_exists(&inode->io_tree, start,
+ start + sectorsize - 1,
+ EXTENT_DELALLOC))) {
+ DEBUG_WARN("fixup worker: delalloc and fixup conflict. ino %llu start %llu",
+ btrfs_ino(inode), start);
+ btrfs_folio_clear_fixup(fs_info, folio, start, sectorsize);
+ continue;
+ }
+ ordered = btrfs_lookup_ordered_range(inode, start, sectorsize);
+ if (ordered) {
+ trace_btrfs_writepage_fixup_defer(inode, ordered);
+ btrfs_unlock_extent(&inode->io_tree, page_start,
+ page_end, &cached_state);
+ folio_unlock(folio);
+ btrfs_start_ordered_extent(ordered);
+ btrfs_put_ordered_extent(ordered);
+ goto again;
+ }
+ ret = btrfs_set_extent_delalloc(inode, start,
+ start + sectorsize - 1, 0,
+ &cached_state);
+ if (ret)
+ break;
+ trace_btrfs_writepage_fixup_reserve(inode, start, sectorsize);
+ btrfs_folio_clear_fixup(fs_info, folio, start, sectorsize);
+ set_bit(bit, delalloc_bitmap);
+ }
+
+ btrfs_unlock_extent(&inode->io_tree, page_start, page_end, &cached_state);
+out:
+ if (ret < 0) {
+ /* Failure here is analogous to failure in writeback. */
+ mapping_set_error(folio->mapping, ret);
+ btrfs_folio_clear_fixup_dirty(fs_info, folio, page_start,
+ folio_size(folio));
+ }
+ if (reserved) {
+ btrfs_delalloc_release_extents(inode, folio_size(folio));
+ for_each_clear_bitrange(start_bit, end_bit, delalloc_bitmap,
+ blocks_per_folio)
+ btrfs_delalloc_release_space(inode, data_reserved,
+ page_start + (start_bit << fs_info->sectorsize_bits),
+ (end_bit - start_bit) << fs_info->sectorsize_bits,
+ true);
+ }
+ folio_unlock(folio);
+ folio_put(folio);
+ kfree(fixup);
+ extent_changeset_free(data_reserved);
+ btrfs_add_delayed_iput(inode);
+}
+
+/*
+ * Queue space reservation fixup work for blocks dirtied without a space reservation.
+ *
+ * Should be used by writeback while holding the folio locked.
+ *
+ * If we fail to queue fixup, then the folio state is unchanged and a future
+ * writeback pass will still see it.
+ */
+void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio)
+{
+ struct btrfs_fs_info *fs_info = inode->root->fs_info;
+ struct btrfs_writepage_fixup *fixup;
+
+ /*
+ * Disallow queueing more fixup during unmount to break the cycle
+ * of writeback queuing fixup queuing writeback etc.
+ *
+ * If it actually hit, then something which was fixup wasn't written
+ * which we should warn about.
+ */
+ if (btrfs_fs_closing(fs_info)) {
+ btrfs_warn_rl(fs_info,
+ "dropping unqueued fixup blocks at unmount. root %lld ino %llu folio %llu",
+ btrfs_root_id(inode->root), btrfs_ino(inode),
+ folio_pos(folio));
+ btrfs_folio_clear_fixup_dirty(fs_info, folio,
+ folio_pos(folio), folio_size(folio));
+ return;
+ }
+
+ fixup = kzalloc_obj(*fixup, GFP_NOFS);
+ if (!fixup)
+ return;
+
+ /*
+ * This is called from within extent_write_cache_pages() which
+ * has successfully done an igrab(). But that will be released at the
+ * end of the writeback pass. We need to extend it for the worker as well.
+ */
+ ihold(&inode->vfs_inode);
+ folio_get(folio);
+ INIT_WORK(&fixup->work, btrfs_writepage_fixup_worker);
+ fixup->folio = folio;
+ fixup->inode = inode;
+ queue_work(fs_info->fixup_workers, &fixup->work);
+}
+
/*
* Clear the old accounting flags and set EXTENT_DELALLOC for the range.
*
@@ -3938,10 +4112,11 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path
btrfs_inode_split_flags(btrfs_inode_flags(leaf, inode_item),
&inode->flags, &inode->ro_flags);
+
+cache_index:
btrfs_update_inode_mapping_flags(inode);
btrfs_set_inode_mapping_order(inode);
-cache_index:
/*
* If we were modified in the current generation and evicted from memory
* and then re-read we need to do a full sync since we don't have any
@@ -7508,6 +7683,12 @@ static void btrfs_invalidate_folio(struct folio *folio, size_t offset,
wait_subpage_spinlock(folio);
/*
+ * The invalidated blocks are going away; drop any fixup blocks among
+ * them, data included, as they have no space reservation.
+ */
+ btrfs_folio_clear_fixup_dirty(fs_info, folio, page_start + offset, length);
+
+ /*
* For subpage case, we have call sites like
* btrfs_punch_hole_lock_range() which passes range not aligned to
* sectorsize.
@@ -9954,6 +10135,7 @@ out_cb:
if (cb)
cleanup_compressed_bio(cb);
out:
+ extent_changeset_free(data_reserved);
if (ret >= 0)
iocb->ki_pos += encoded->len;
return ret;
@@ -10549,6 +10731,41 @@ static const struct file_operations btrfs_dir_file_operations = {
};
/*
+ * The folio is going dirty without a btrfs delalloc space reservation.
+ * This requires a fixup before writeback which we might sleep so cannot
+ * run in this context, so we merely set state on the folio indicating it
+ * needs fixup before writeback.
+ *
+ * Note that there is no range in the input, so the whole folio is marked
+ * dirty and fixup.
+ *
+ * We believe that all callers of dirty_folio either:
+ * - take the folio lock (e.g. pinned folio release notification).
+ * - take the pte lock but must be running on a dirty pte which means
+ * page_mkwrite() ran on it and reserved the space. zap_pte_range() cannot
+ * race with writeback cleaning the folio because writeback runs
+ * folio_mkclean() which also uses the pte lock and revokes outstanding
+ * writable mappings.
+ * Therefore, an additional folio private lock (a la bfs->lock for all cases,
+ * not just subpage) is not necessary.
+ */
+static bool btrfs_data_dirty_folio(struct address_space *mapping,
+ struct folio *folio)
+{
+ struct btrfs_inode *inode = BTRFS_I(mapping->host);
+ struct btrfs_fs_info *fs_info = inode->root->fs_info;
+ const u64 page_start = folio_pos(folio);
+ const u64 range_end = min_t(u64, folio_next_pos(folio),
+ round_up(i_size_read(&inode->vfs_inode),
+ fs_info->sectorsize));
+
+ if (range_end > page_start)
+ btrfs_folio_set_fixup_dirty(fs_info, folio, page_start,
+ range_end - page_start);
+ return filemap_dirty_folio(mapping, folio);
+}
+
+/*
* btrfs doesn't support the bmap operation because swapfiles
* use bmap to make a mapping of extents in the file. They assume
* these extents won't change over the life of the file and they
@@ -10568,7 +10785,7 @@ static const struct address_space_operations btrfs_aops = {
.launder_folio = btrfs_launder_folio,
.release_folio = btrfs_release_folio,
.migrate_folio = btrfs_migrate_folio,
- .dirty_folio = filemap_dirty_folio,
+ .dirty_folio = btrfs_data_dirty_folio,
.error_remove_folio = generic_error_remove_folio,
.swap_activate = btrfs_swap_activate,
.swap_deactivate = btrfs_swap_deactivate,
diff --git a/fs/btrfs/lzo.c b/fs/btrfs/lzo.c
index 1531adb117d1..2f0996692da0 100644
--- a/fs/btrfs/lzo.c
+++ b/fs/btrfs/lzo.c
@@ -552,9 +552,10 @@ int lzo_decompress(struct list_head *ws, const u8 *data_in,
size_t max_segment_len = workspace_buf_length(fs_info);
int ret;
- if (unlikely(srclen < LZO_LEN || srclen > max_segment_len + LZO_LEN * 2)) {
+ if (unlikely(srclen <= LZO_LEN * 2 ||
+ srclen > max_segment_len + LZO_LEN * 2)) {
btrfs_err(fs_info, "invalid lzo header length, has %zu expect (%u, %zu)",
- srclen, LZO_LEN, max_segment_len + LZO_LEN * 2);
+ srclen, LZO_LEN * 2, max_segment_len + LZO_LEN * 2);
return -EUCLEAN;
}
diff --git a/fs/btrfs/subpage.c b/fs/btrfs/subpage.c
index 2a9397be8116..27dd677ca687 100644
--- a/fs/btrfs/subpage.c
+++ b/fs/btrfs/subpage.c
@@ -345,18 +345,57 @@ void btrfs_subpage_clear_uptodate(const struct btrfs_fs_info *fs_info,
spin_unlock_irqrestore(&bfs->lock, flags);
}
+/*
+ * folio_mark_dirty() for a folio we are dirtying with a space reservation.
+ *
+ * Dirtiers without a reservation use btrfs_data_dirty_folio().
+ */
+static void btrfs_folio_mark_dirty(struct folio *folio)
+{
+ struct address_space *mapping = folio_mapping(folio);
+
+ if (!mapping || !mapping->host || !is_data_inode(BTRFS_I(mapping->host))) {
+ folio_mark_dirty(folio);
+ return;
+ }
+ if (folio_test_reclaim(folio))
+ folio_clear_reclaim(folio);
+ filemap_dirty_folio(mapping, folio);
+}
+
+/*
+ * The set helper of the dirty ops, so it only runs for folios without a
+ * fixup bitmap: for those the folio flag is the whole fixup state, and this
+ * reserving write covers the block, so retire it. Metadata never has the
+ * flag set and only pays the test.
+ */
+static void btrfs_folio_mark_dirty_reserved(struct folio *folio)
+{
+ if (folio_test_fixup_pending(folio))
+ folio_clear_fixup_pending(folio);
+ btrfs_folio_mark_dirty(folio);
+}
+
void btrfs_subpage_set_dirty(const struct btrfs_fs_info *fs_info,
struct folio *folio, u64 start, u32 len)
{
struct btrfs_folio_state *bfs = folio_get_private(folio);
- unsigned int start_bit = subpage_calc_start_bit(fs_info, folio,
+ unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio,
dirty, start, len);
+ unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio,
+ fixup, start, len);
+ const unsigned int nbits = len >> fs_info->sectorsize_bits;
unsigned long flags;
spin_lock_irqsave(&bfs->lock, flags);
- bitmap_set(bfs->bitmaps, start_bit, len >> fs_info->sectorsize_bits);
+ bitmap_set(bfs->bitmaps, dirty_bit, nbits);
+ /* Proper dirtying obviates the need for fixup. */
+ bitmap_clear(bfs->bitmaps, fixup_bit, nbits);
+ if (folio_test_fixup_pending(folio) &&
+ subpage_test_bitmap_all_zero(fs_info, folio, fixup))
+ folio_clear_fixup_pending(folio);
spin_unlock_irqrestore(&bfs->lock, flags);
- folio_mark_dirty(folio);
+ btrfs_folio_mark_dirty(folio);
}
static void folio_clear_tags(struct folio *folio)
@@ -457,6 +496,172 @@ void btrfs_subpage_clear_writeback(const struct btrfs_fs_info *fs_info,
spin_unlock_irqrestore(&bfs->lock, flags);
}
+void btrfs_subpage_clear_fixup(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len)
+{
+ struct btrfs_folio_state *bfs = folio_get_private(folio);
+ unsigned int start_bit = subpage_calc_start_bit(fs_info, folio,
+ fixup, start, len);
+ unsigned long flags;
+
+ spin_lock_irqsave(&bfs->lock, flags);
+ bitmap_clear(bfs->bitmaps, start_bit, len >> fs_info->sectorsize_bits);
+ if (subpage_test_bitmap_all_zero(fs_info, folio, fixup))
+ folio_clear_fixup_pending(folio);
+ spin_unlock_irqrestore(&bfs->lock, flags);
+}
+
+/*
+ * In one pass under bfs->lock, mark every block with a clear dirty bit in the
+ * range both dirty and needing fixup.
+ *
+ * Only called from the dirty_folio callback, which owns the folio-level
+ * dirty flag; calling folio_mark_dirty() here would recurse.
+ *
+ * The folio fixup flag and bits are both set under bfs->lock so that a
+ * writeback pass observing the new bits also observes the flag.
+ */
+static void btrfs_subpage_set_fixup_dirty(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len)
+{
+ struct btrfs_folio_state *bfs = folio_get_private(folio);
+ unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio,
+ dirty, start, len);
+ unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio,
+ fixup, start, len);
+ const unsigned int nbits = len >> fs_info->sectorsize_bits;
+ unsigned long flags;
+ bool marked = false;
+
+ spin_lock_irqsave(&bfs->lock, flags);
+ for (unsigned int i = 0; i < nbits; i++) {
+ if (test_bit(dirty_bit + i, bfs->bitmaps))
+ continue;
+ set_bit(dirty_bit + i, bfs->bitmaps);
+ set_bit(fixup_bit + i, bfs->bitmaps);
+ marked = true;
+ }
+ if (marked)
+ folio_set_fixup_pending(folio);
+ spin_unlock_irqrestore(&bfs->lock, flags);
+}
+
+/*
+ * Mark the still-clean blocks of a folio dirty and needing fixup, for
+ * btrfs_data_dirty_folio().
+ *
+ * A subpage block size folio that is not uptodate is left alone: its clean
+ * blocks may hold content that was never read in, which must not be marked
+ * dirty.
+ */
+void btrfs_folio_set_fixup_dirty(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len)
+{
+ if (!btrfs_is_subpage(fs_info, folio)) {
+ if (!folio_test_dirty(folio))
+ folio_set_fixup_pending(folio);
+ return;
+ }
+ if (!folio_test_uptodate(folio))
+ return;
+ btrfs_subpage_set_fixup_dirty(fs_info, folio, start, len);
+}
+
+/*
+ * Drop the fixup blocks inside the range: clear both their fixup and dirty
+ * bits.
+ *
+ * Fixup blocks carry no space reservation, so their fixup and dirty bits
+ * must be dropped together. Clearing only the fixup bit would leave a
+ * dirty block without a reservation which is not a valid state.
+ *
+ * Returns true if the folio has no dirty blocks left.
+ */
+static bool btrfs_subpage_clear_fixup_dirty(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len)
+{
+ struct btrfs_folio_state *bfs = folio_get_private(folio);
+ unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio,
+ dirty, start, len);
+ unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio,
+ fixup, start, len);
+ const unsigned int nbits = len >> fs_info->sectorsize_bits;
+ unsigned long flags;
+ bool last;
+
+ spin_lock_irqsave(&bfs->lock, flags);
+ for (unsigned int i = 0; i < nbits; i++) {
+ if (!test_bit(fixup_bit + i, bfs->bitmaps))
+ continue;
+ clear_bit(fixup_bit + i, bfs->bitmaps);
+ clear_bit(dirty_bit + i, bfs->bitmaps);
+ }
+ if (subpage_test_bitmap_all_zero(fs_info, folio, fixup))
+ folio_clear_fixup_pending(folio);
+ last = subpage_test_bitmap_all_zero(fs_info, folio, dirty);
+ spin_unlock_irqrestore(&bfs->lock, flags);
+ return last;
+}
+
+/*
+ * Drop the fixup blocks inside the range, for callers discarding their data:
+ * btrfs_invalidate_folio() and the writepage fixup worker's error path.
+ *
+ * Callers that have just reserved space for a block want
+ * btrfs_folio_clear_fixup() instead - there the block stays dirty and gets
+ * written.
+ *
+ * The range can be byte-granular (an unaligned truncate through
+ * btrfs_invalidate_folio()); only blocks fully inside it are dropped, as a
+ * partially covered block still holds live data outside the range. For
+ * single-block folios the folio flag is the fixup state, so it is dropped
+ * only when the range covers the whole folio.
+ */
+void btrfs_folio_clear_fixup_dirty(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len)
+{
+ u64 aligned_start;
+ u64 aligned_end;
+
+ /* The folio flag is set whenever any fixup bitmap bit is. */
+ if (!folio_test_fixup_pending(folio))
+ return;
+ if (!btrfs_is_subpage(fs_info, folio)) {
+ if (start <= folio_pos(folio) &&
+ start + len >= folio_next_pos(folio)) {
+ folio_clear_fixup_pending(folio);
+ folio_clear_dirty_for_io(folio);
+ }
+ return;
+ }
+ btrfs_subpage_clamp_range(folio, &start, &len);
+ aligned_start = round_up(start, fs_info->sectorsize);
+ aligned_end = round_down(start + len, fs_info->sectorsize);
+ if (aligned_end <= aligned_start)
+ return;
+ if (btrfs_subpage_clear_fixup_dirty(fs_info, folio, aligned_start,
+ aligned_end - aligned_start))
+ folio_clear_dirty_for_io(folio);
+}
+
+bool btrfs_folio_test_fixup(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len)
+{
+ if (!btrfs_is_subpage(fs_info, folio))
+ return folio_test_fixup_pending(folio);
+ return btrfs_subpage_test_fixup(fs_info, folio, start, len);
+}
+
+void btrfs_folio_clear_fixup(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len)
+{
+ if (!btrfs_is_subpage(fs_info, folio)) {
+ folio_clear_fixup_pending(folio);
+ return;
+ }
+ btrfs_subpage_clear_fixup(fs_info, folio, start, len);
+}
+
/*
* Unlike set/clear which is dependent on each page status, for test all bits
* are tested in the same way.
@@ -480,6 +685,7 @@ bool btrfs_subpage_test_##name(const struct btrfs_fs_info *fs_info, \
IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(uptodate);
IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(dirty);
IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(writeback);
+IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(fixup);
/*
* Note that, in selftests (extent-io-tests), we can have empty fs_info passed
@@ -571,8 +777,8 @@ bool btrfs_meta_folio_test_##name(struct folio *folio, const struct extent_buffe
}
IMPLEMENT_BTRFS_PAGE_OPS(uptodate, folio_mark_uptodate, folio_clear_uptodate,
folio_test_uptodate);
-IMPLEMENT_BTRFS_PAGE_OPS(dirty, folio_mark_dirty, folio_clear_dirty_for_io,
- folio_test_dirty);
+IMPLEMENT_BTRFS_PAGE_OPS(dirty, btrfs_folio_mark_dirty_reserved,
+ folio_clear_dirty_for_io, folio_test_dirty);
IMPLEMENT_BTRFS_PAGE_OPS(writeback, folio_start_writeback, folio_end_writeback,
folio_test_writeback);
diff --git a/fs/btrfs/subpage.h b/fs/btrfs/subpage.h
index c6d7394e6418..9aceba93c818 100644
--- a/fs/btrfs/subpage.h
+++ b/fs/btrfs/subpage.h
@@ -14,15 +14,15 @@ struct folio;
/*
* Extra info for subpage bitmap.
*
- * For subpage we pack all uptodate/dirty/writeback bitmaps into
+ * For subpage we pack all uptodate/dirty/writeback/fixup bitmaps into
* one larger bitmap.
*
* This structure records how they are organized in the bitmap:
*
- * /- uptodate /- dirty /- writeback
- * | | |
- * v v v
- * |u|u|u|u|........|u|u|d|d|.......|d|d|w|w|.......|w|w|
+ * /- uptodate /- dirty /- writeback /- fixup
+ * | | | |
+ * v v v v
+ * |u|u|u|u|........|u|u|d|d|.......|d|d|w|w|.....|w|w|f|f|.....|f|f|
* |< sectors_per_page >|
*
* Unlike regular macro-like enums, here we do not go upper-case names, as
@@ -40,6 +40,14 @@ enum {
*/
btrfs_bitmap_nr_writeback,
+ /*
+ * Blocks dirtied by the dirty_folio callback instead of a reserving
+ * write path (e.g. set_page_dirty_lock() on a GUP pin). They have
+ * no space reservation and need the writepage fixup before they can
+ * be submitted.
+ */
+ btrfs_bitmap_nr_fixup,
+
btrfs_bitmap_nr_max
};
@@ -166,6 +174,29 @@ DECLARE_BTRFS_SUBPAGE_OPS(dirty);
DECLARE_BTRFS_SUBPAGE_OPS(writeback);
/*
+ * Fixup bit helpers.
+ *
+ * The fixup bit is data-only and has no plain set helper (setting happens
+ * together with dirtying in btrfs_subpage_set_fixup_dirty()), so it does not
+ * go through DECLARE_BTRFS_SUBPAGE_OPS(). For single-block folios the
+ * folio_*_fixup_pending() flag takes the place of the bitmap.
+ */
+void btrfs_subpage_clear_fixup(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len);
+bool btrfs_subpage_test_fixup(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len);
+bool btrfs_folio_test_fixup(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len);
+void btrfs_folio_set_fixup_dirty(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len);
+/* For a block that just got its space reserved; it stays dirty. */
+void btrfs_folio_clear_fixup(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len);
+/* For callers discarding the data; clears the dirty bits too. */
+void btrfs_folio_clear_fixup_dirty(const struct btrfs_fs_info *fs_info,
+ struct folio *folio, u64 start, u32 len);
+
+/*
* Helper for error cleanup, where a folio will have its dirty flag cleared,
* with writeback started and finished.
*/
diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c
index 9915e39362db..c80b24a941ad 100644
--- a/fs/crypto/policy.c
+++ b/fs/crypto/policy.c
@@ -534,7 +534,7 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg)
return -EFAULT;
policy.version = version;
- if (!inode_owner_or_capable(&nop_mnt_idmap, inode))
+ if (!inode_owner_or_capable(file_mnt_idmap(filp), inode))
return -EACCES;
ret = mnt_want_write_file(filp);
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 1360409d8de9..5709c6fea85b 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -10364,6 +10364,7 @@ static void nfs41_free_stateid_release(void *calldata)
struct nfs_free_stateid_data *data = calldata;
struct nfs_client *clp = data->server->nfs_client;
+ nfs_sb_deactive(data->server->super);
nfs_put_client(clp);
kfree(calldata);
}
@@ -10402,17 +10403,22 @@ static int nfs41_free_stateid(struct nfs_server *server,
struct nfs_free_stateid_data *data;
struct rpc_task *task;
struct nfs_client *clp = server->nfs_client;
+ int ret = -EIO;
if (!refcount_inc_not_zero(&clp->cl_count))
- return -EIO;
+ return ret;
+ if (!nfs_sb_active(server->super))
+ goto out_put_clp;
nfs4_state_protect(clp, NFS_SP4_MACH_CRED_STATEID,
&task_setup.rpc_client, &msg);
dprintk("NFS call free_stateid %p\n", stateid);
data = kmalloc_obj(*data);
- if (!data)
- return -ENOMEM;
+ if (!data) {
+ ret = -ENOMEM;
+ goto out_put_server;
+ }
data->server = server;
nfs4_stateid_copy(&data->args.stateid, stateid);
@@ -10428,6 +10434,11 @@ static int nfs41_free_stateid(struct nfs_server *server,
rpc_put_task(task);
stateid->type = NFS4_FREED_STATEID_TYPE;
return 0;
+out_put_server:
+ nfs_sb_deactive(server->super);
+out_put_clp:
+ nfs_put_client(clp);
+ return ret;
}
static void
@@ -10585,7 +10596,8 @@ const struct nfs4_minor_version_ops *nfs_v4_minor_ops[] = {
static ssize_t nfs4_listxattr(struct dentry *dentry, char *list, size_t size)
{
ssize_t error, error2, error3;
- size_t left = size;
+ ssize_t left = size;
+ ssize_t left2;
error = generic_listxattr(dentry, list, left);
if (error < 0)
@@ -10595,10 +10607,13 @@ static ssize_t nfs4_listxattr(struct dentry *dentry, char *list, size_t size)
left -= error;
}
- error2 = security_inode_listsecurity(d_inode(dentry), &list, &left);
+ left2 = left;
+ error2 = security_inode_listsecurity(d_inode(dentry), &list, &left2);
if (error2 < 0)
return error2;
- error2 = size - error - left;
+ error2 = left - left2;
+ if (list)
+ left -= error2;
error3 = nfs4_listxattr_nfs4_user(d_inode(dentry), list, left);
if (error3 < 0)
diff --git a/fs/smb/client/cifsfs.c b/fs/smb/client/cifsfs.c
index 1788d93a2522..a1dacc7d8f74 100644
--- a/fs/smb/client/cifsfs.c
+++ b/fs/smb/client/cifsfs.c
@@ -692,6 +692,8 @@ cifs_show_options(struct seq_file *s, struct dentry *root)
seq_puts(s, ",seal");
else if (tcon->ses->server->ignore_signature)
seq_puts(s, ",signloosely");
+ if (cifs_sb->ctx->compress)
+ seq_puts(s, ",compress");
if (tcon->nocase)
seq_puts(s, ",nocase");
if (tcon->nodelete)
diff --git a/fs/smb/client/sess.c b/fs/smb/client/sess.c
index de2012cc9cf3..7cf7dd104f7c 100644
--- a/fs/smb/client/sess.c
+++ b/fs/smb/client/sess.c
@@ -233,9 +233,9 @@ int cifs_try_adding_channels(struct cifs_ses *ses)
cifs_dbg(VFS, "failed to open extra channel on iface:%pIS rc=%d\n",
&iface->sockaddr,
rc);
- kref_put(&iface->refcount, release_iface);
/* failure to add chan should increase weight */
iface->weight_fulfilled++;
+ kref_put(&iface->refcount, release_iface);
continue;
}
diff --git a/fs/smb/client/smb1transport.c b/fs/smb/client/smb1transport.c
index 53abb29fe71b..966f2cf83a51 100644
--- a/fs/smb/client/smb1transport.c
+++ b/fs/smb/client/smb1transport.c
@@ -260,9 +260,23 @@ SendReceive(const unsigned int xid, struct cifs_ses *ses,
goto out;
if (out_buf) {
- *pbytes_returned = resp_iov.iov_len;
- if (resp_iov.iov_len)
- memcpy(out_buf, resp_iov.iov_base, resp_iov.iov_len);
+ /* Use smbCalcSize() for both single- and multi-part T2 responses,
+ * both here and in coalesce_t2().
+ */
+ unsigned int copy_len;
+ if (WARN_ON_ONCE(!resp_iov.iov_base)) {
+ rc = -EIO;
+ goto out;
+ }
+ copy_len = smbCalcSize(resp_iov.iov_base);
+ if (copy_len > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) {
+ cifs_dbg(VFS, "response size %u exceeds buffer\n",
+ copy_len);
+ rc = -ENOBUFS;
+ goto out;
+ }
+ *pbytes_returned = copy_len;
+ memcpy(out_buf, resp_iov.iov_base, copy_len);
}
out:
@@ -386,11 +400,13 @@ coalesce_t2(char *second_buf, struct smb_hdr *target_hdr, unsigned int *pdu_len)
}
put_bcc(byte_count, target_hdr);
- byte_count = *pdu_len;
- byte_count += total_in_src;
+ /* use smbCalcSize() rather than *pdu_len: the demux loop resets
+ * *pdu_len to each secondary's pdu_length, making it unreliable.
+ */
+ byte_count = smbCalcSize(target_hdr);
/* don't allow buffer to overflow */
if (byte_count > CIFSMaxBufSize + MAX_CIFS_HDR_SIZE) {
- cifs_dbg(FYI, "coalesced BCC exceeds buffer size (%u)\n",
+ cifs_dbg(FYI, "coalesced size exceeds buffer size (%u)\n",
byte_count);
return -ENOBUFS;
}
diff --git a/fs/smb/common/compress/compress.c b/fs/smb/common/compress/compress.c
index b07a317597a4..a4123c8f1c0a 100644
--- a/fs/smb/common/compress/compress.c
+++ b/fs/smb/common/compress/compress.c
@@ -95,6 +95,7 @@ static int smb_decompress_lz77_payload(const u8 **src, u32 *slen, u8 **dst,
}
static int smb_decompress_chained(__le16 alg, bool allow_chained,
+ bool allow_pattern,
const struct smb2_compression_hdr *hdr,
u32 slen, void *dst, u32 dlen)
{
@@ -143,6 +144,8 @@ static int smb_decompress_chained(__le16 alg, bool allow_chained,
rc = smb_decompress_none(&src, &remaining, &out,
&out_remaining, len);
} else if (payload_alg == SMB3_COMPRESS_PATTERN) {
+ if (!allow_pattern)
+ return -EINVAL;
rc = smb_decompress_pattern(&src, &remaining, &out,
&out_remaining, len);
} else if (payload_alg == alg && alg == SMB3_COMPRESS_LZ77) {
@@ -185,6 +188,7 @@ static int smb_decompress_unchained(__le16 alg,
* smb_compression_decompress() - decode an SMB2 compression transform
* @alg: negotiated general-purpose compression algorithm
* @allow_chained: whether chained transforms were negotiated
+ * @allow_pattern: whether Pattern_V1 payloads were negotiated
* @src: transform header followed by compressed payload data
* @slen: total number of bytes available at @src
* @dst: output buffer for the reconstructed SMB2 message
@@ -197,7 +201,8 @@ static int smb_decompress_unchained(__le16 alg,
* Return: 0 on success, otherwise a negative errno.
*/
int smb_compression_decompress(__le16 alg, bool allow_chained,
- const void *src, u32 slen, void *dst, u32 dlen)
+ bool allow_pattern, const void *src, u32 slen,
+ void *dst, u32 dlen)
{
const struct smb2_compression_hdr *hdr = src;
@@ -207,8 +212,8 @@ int smb_compression_decompress(__le16 alg, bool allow_chained,
return -EINVAL;
if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED))
- return smb_decompress_chained(alg, allow_chained, hdr, slen,
- dst, dlen);
+ return smb_decompress_chained(alg, allow_chained, allow_pattern,
+ hdr, slen, dst, dlen);
if (hdr->Flags != cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE))
return -EINVAL;
diff --git a/fs/smb/common/compress/compress.h b/fs/smb/common/compress/compress.h
index 7ace3bf4b664..d6916669f887 100644
--- a/fs/smb/common/compress/compress.h
+++ b/fs/smb/common/compress/compress.h
@@ -20,7 +20,8 @@ static __always_inline bool smb_compress_alg_valid(__le16 alg, bool valid_none)
}
int smb_compression_decompress(__le16 alg, bool allow_chained,
- const void *src, u32 slen, void *dst, u32 dlen);
+ bool allow_pattern, const void *src, u32 slen,
+ void *dst, u32 dlen);
int smb_compression_compress_chained(__le16 alg, bool allow_pattern,
const void *src, u32 slen,
void *dst, u32 *dlen);
diff --git a/fs/smb/server/compress.c b/fs/smb/server/compress.c
index 95e48fa6b448..01d1771ff663 100644
--- a/fs/smb/server/compress.c
+++ b/fs/smb/server/compress.c
@@ -46,16 +46,25 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn)
return -EINVAL;
orig_size = le32_to_cpu(hdr->OriginalCompressedSegmentSize);
+ /*
+ * For chained transforms the top-level header is only eight bytes; the
+ * Flags field overlays the first payload header. Reject unknown Flags
+ * and unnegotiated chained mode before allocating the output buffer.
+ */
if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_CHAINED)) {
+ if (!conn->compress_chained)
+ return -EINVAL;
out_size = orig_size;
- } else {
+ } else if (hdr->Flags == cpu_to_le16(SMB2_COMPRESSION_FLAG_NONE)) {
offset = le32_to_cpu(hdr->Offset);
if (offset > pdu_size - sizeof(*hdr) ||
check_add_overflow(orig_size, offset, &out_size))
return -EINVAL;
+ } else {
+ return -EINVAL;
}
- max_allowed_pdu_size = SMB3_MAX_MSGSIZE + conn->vals->max_write_size;
+ max_allowed_pdu_size = ksmbd_max_allowed_pdu_size(conn);
if (out_size < sizeof(struct smb2_pdu) ||
out_size > max_allowed_pdu_size ||
out_size > MAX_STREAM_PROT_LEN)
@@ -69,6 +78,7 @@ int ksmbd_decompress_request(struct ksmbd_conn *conn)
*(__be32 *)out = cpu_to_be32(out_size);
rc = smb_compression_decompress(conn->compress_algorithm,
conn->compress_chained,
+ conn->compress_pattern,
buf, pdu_size, out + 4, out_size);
if (rc) {
kvfree(out);
diff --git a/fs/smb/server/connection.c b/fs/smb/server/connection.c
index dee8e4aced99..ef6f202f4024 100644
--- a/fs/smb/server/connection.c
+++ b/fs/smb/server/connection.c
@@ -488,11 +488,7 @@ recheck:
pdu_size = get_rfc1002_len(hdr_buf);
ksmbd_debug(CONN, "RFC1002 header %u bytes\n", pdu_size);
- if (ksmbd_conn_good(conn))
- max_allowed_pdu_size =
- SMB3_MAX_MSGSIZE + conn->vals->max_write_size;
- else
- max_allowed_pdu_size = SMB3_MAX_MSGSIZE;
+ max_allowed_pdu_size = ksmbd_max_allowed_pdu_size(conn);
if (pdu_size > max_allowed_pdu_size) {
pr_err_ratelimited("PDU length(%u) exceeded maximum allowed pdu size(%u) on connection(%d)\n",
diff --git a/fs/smb/server/connection.h b/fs/smb/server/connection.h
index 2a194ee36fb4..0e4ebfac5558 100644
--- a/fs/smb/server/connection.h
+++ b/fs/smb/server/connection.h
@@ -210,6 +210,15 @@ static inline bool ksmbd_conn_good(struct ksmbd_conn *conn)
return READ_ONCE(conn->status) == KSMBD_SESS_GOOD;
}
+static inline unsigned int
+ksmbd_max_allowed_pdu_size(struct ksmbd_conn *conn)
+{
+ if (ksmbd_conn_good(conn))
+ return SMB3_MAX_MSGSIZE + conn->vals->max_write_size;
+
+ return SMB3_MAX_MSGSIZE;
+}
+
static inline bool ksmbd_conn_need_negotiate(struct ksmbd_conn *conn)
{
return READ_ONCE(conn->status) == KSMBD_SESS_NEED_NEGOTIATE;
diff --git a/fs/verity/measure.c b/fs/verity/measure.c
index cfe2d5e535f9..68dfccb69772 100644
--- a/fs/verity/measure.c
+++ b/fs/verity/measure.c
@@ -122,11 +122,11 @@ __bpf_kfunc int bpf_get_fsverity_digest(struct file *file, const struct bpf_dynp
{
const struct bpf_dynptr_kern *digest_ptr = (struct bpf_dynptr_kern *)digest_p;
const struct inode *inode = file_inode(file);
- u32 dynptr_sz = __bpf_dynptr_size(digest_ptr);
+ u64 dynptr_sz = __bpf_dynptr_size(digest_ptr);
struct fsverity_digest *arg;
const struct fsverity_info *vi;
const struct fsverity_hash_alg *hash_alg;
- int out_digest_sz;
+ u64 out_digest_sz;
if (dynptr_sz < sizeof(struct fsverity_digest))
return -EINVAL;
@@ -144,17 +144,20 @@ __bpf_kfunc int bpf_get_fsverity_digest(struct file *file, const struct bpf_dynp
hash_alg = vi->tree_params.hash_alg;
+ out_digest_sz = dynptr_sz - sizeof(struct fsverity_digest);
+ if (out_digest_sz < hash_alg->digest_size)
+ return -EOVERFLOW;
+
arg->digest_algorithm = hash_alg - fsverity_hash_algs;
arg->digest_size = hash_alg->digest_size;
- out_digest_sz = dynptr_sz - sizeof(struct fsverity_digest);
-
/* copy digest */
- memcpy(arg->digest, vi->file_digest, min_t(int, hash_alg->digest_size, out_digest_sz));
+ memcpy(arg->digest, vi->file_digest, hash_alg->digest_size);
/* fill the extra buffer with zeros */
if (out_digest_sz > hash_alg->digest_size)
- memset(arg->digest + arg->digest_size, 0, out_digest_sz - hash_alg->digest_size);
+ memset(arg->digest + hash_alg->digest_size, 0,
+ out_digest_sz - hash_alg->digest_size);
return 0;
}
diff --git a/fs/xfs/libxfs/xfs_exchmaps.c b/fs/xfs/libxfs/xfs_exchmaps.c
index dcd0bd0b13b4..3efed37cb98a 100644
--- a/fs/xfs/libxfs/xfs_exchmaps.c
+++ b/fs/xfs/libxfs/xfs_exchmaps.c
@@ -959,6 +959,16 @@ xmi_can_exchange_reflink_flags(
{
struct xfs_mount *mp = req->ip1->i_mount;
+ /*
+ * The INO1_WRITTEN optimization can skip exchanging hole and
+ * unwritten mappings, which means we cannot guarantee that all
+ * shared extents actually moved to the other file. Clearing the
+ * reflink flag of an inode that still holds shared extents breaks
+ * the CoW write path, so refuse to exchange the flags in that case.
+ */
+ if (req->flags & XFS_EXCHMAPS_INO1_WRITTEN)
+ return false;
+
if (hweight32(reflink_state) != 1)
return false;
if (req->startoff1 != 0 || req->startoff2 != 0)
diff --git a/fs/xfs/libxfs/xfs_rtrefcount_btree.c b/fs/xfs/libxfs/xfs_rtrefcount_btree.c
index f27b80a199ba..22acc1411aac 100644
--- a/fs/xfs/libxfs/xfs_rtrefcount_btree.c
+++ b/fs/xfs/libxfs/xfs_rtrefcount_btree.c
@@ -201,7 +201,7 @@ xfs_rtrefcountbt_verify(
if (fa)
return fa;
level = be16_to_cpu(block->bb_level);
- if (level > mp->m_rtrefc_maxlevels)
+ if (level >= mp->m_rtrefc_maxlevels)
return __this_address;
return xfs_btree_fsblock_verify(bp, mp->m_rtrefc_mxr[level != 0]);
@@ -651,7 +651,7 @@ xfs_iformat_rtrefcount(
numrecs = be16_to_cpu(dfp->bb_numrecs);
level = be16_to_cpu(dfp->bb_level);
- if (level > mp->m_rtrefc_maxlevels ||
+ if (level >= mp->m_rtrefc_maxlevels ||
xfs_rtrefcount_droot_space_calc(level, numrecs) > dsize) {
xfs_inode_mark_sick(ip, XFS_SICK_INO_CORE);
return -EFSCORRUPTED;
diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c
index 47322adb7690..75f2a021ee6d 100644
--- a/fs/xfs/libxfs/xfs_sb.c
+++ b/fs/xfs/libxfs/xfs_sb.c
@@ -1118,10 +1118,10 @@ xfs_sb_read_verify(
* because _verify_common checks the on-disk values.
*/
__xfs_sb_from_disk(&sb, dsb, false);
- error = xfs_validate_sb_common(mp, bp, &sb);
+ error = xfs_validate_sb_read(mp, &sb);
if (error)
goto out_error;
- error = xfs_validate_sb_read(mp, &sb);
+ error = xfs_validate_sb_common(mp, bp, &sb);
out_error:
if (error == -EFSCORRUPTED || error == -EFSBADCRC)
diff --git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c
index 9ed053b5f061..1fa66aa68e16 100644
--- a/fs/xfs/scrub/agheader.c
+++ b/fs/xfs/scrub/agheader.c
@@ -18,6 +18,8 @@
#include "xfs_inode.h"
#include "scrub/scrub.h"
#include "scrub/common.h"
+#include "scrub/bitmap.h"
+#include "scrub/agino_bitmap.h"
int
xchk_setup_agheader(
@@ -266,7 +268,7 @@ xchk_superblock(
xchk_block_set_corrupt(sc, bp);
if (sb->sb_gquotino != cpu_to_be64(0))
- xchk_block_set_preen(sc, bp);
+ xchk_block_set_corrupt(sc, bp);
} else {
if (sb->sb_uquotino != cpu_to_be64(mp->m_sb.sb_uquotino))
xchk_block_set_preen(sc, bp);
@@ -933,40 +935,84 @@ xchk_agi_xref(
}
/*
+ * Walk the incore unlinked list for a particular AGI bucket to construct
+ * the unlinked inode bitmap for later reconstruction of the unlinked list.
+ * Returns 1 if we should keep checking, 0 to stop checking, or a negative
+ * errno.
+ */
+static int
+xchk_iunlink_bucket(
+ struct xfs_scrub *sc,
+ unsigned int bucket,
+ xfs_agino_t agino)
+{
+ struct xagino_bitmap seen;
+ int ret;
+
+ xagino_bitmap_init(&seen);
+
+ while (agino != NULLAGINO) {
+ struct xfs_inode *ip;
+ unsigned int len = 1;
+
+ if (agino % XFS_AGI_UNLINKED_BUCKETS != bucket) {
+ xchk_block_set_corrupt(sc, sc->sa.agi_bp);
+ goto bad;
+ }
+
+ if (xagino_bitmap_test(&seen, agino, &len)) {
+ xchk_block_set_corrupt(sc, sc->sa.agi_bp);
+ goto bad;
+ }
+
+ ip = xfs_iunlink_lookup(sc->sa.pag, agino);
+ if (!ip) {
+ xchk_block_set_corrupt(sc, sc->sa.agi_bp);
+ goto bad;
+ }
+
+ if (!xfs_inode_on_unlinked_list(ip)) {
+ xchk_block_set_corrupt(sc, sc->sa.agi_bp);
+ goto bad;
+ }
+
+ ret = xagino_bitmap_set(&seen, agino, 1);
+ if (ret)
+ goto out_bitmap;
+
+ agino = ip->i_next_unlinked;
+ }
+ ret = 1;
+
+out_bitmap:
+ xagino_bitmap_destroy(&seen);
+ return ret;
+bad:
+ ret = 0;
+ goto out_bitmap;
+}
+
+/*
* Check the unlinked buckets for links to bad inodes. We hold the AGI, so
* there cannot be any threads updating unlinked list pointers in this AG.
*/
-STATIC void
+STATIC int
xchk_iunlink(
struct xfs_scrub *sc,
struct xfs_agi *agi)
{
unsigned int i;
- struct xfs_inode *ip;
for (i = 0; i < XFS_AGI_UNLINKED_BUCKETS; i++) {
- xfs_agino_t agino = be32_to_cpu(agi->agi_unlinked[i]);
-
- while (agino != NULLAGINO) {
- if (agino % XFS_AGI_UNLINKED_BUCKETS != i) {
- xchk_block_set_corrupt(sc, sc->sa.agi_bp);
- return;
- }
-
- ip = xfs_iunlink_lookup(sc->sa.pag, agino);
- if (!ip) {
- xchk_block_set_corrupt(sc, sc->sa.agi_bp);
- return;
- }
-
- if (!xfs_inode_on_unlinked_list(ip)) {
- xchk_block_set_corrupt(sc, sc->sa.agi_bp);
- return;
- }
-
- agino = ip->i_next_unlinked;
- }
+ int ret;
+
+ ret = xchk_iunlink_bucket(sc, i,
+ be32_to_cpu(agi->agi_unlinked[i]));
+ if (ret < 1)
+ return ret;
}
+
+ return 0;
}
/* Scrub the AGI. */
@@ -1053,7 +1099,9 @@ xchk_agi(
if (pag->pagi_freecount != be32_to_cpu(agi->agi_freecount))
xchk_block_set_corrupt(sc, sc->sa.agi_bp);
- xchk_iunlink(sc, agi);
+ error = xchk_iunlink(sc, agi);
+ if (error)
+ goto out;
xchk_agi_xref(sc);
out:
diff --git a/fs/xfs/scrub/agheader_repair.c b/fs/xfs/scrub/agheader_repair.c
index 2554494847ff..2104512f1ee1 100644
--- a/fs/xfs/scrub/agheader_repair.c
+++ b/fs/xfs/scrub/agheader_repair.c
@@ -980,6 +980,13 @@ err:
}
/*
+ * Magic value that means "not unlinked" because xfarrays don't support storing
+ * totally zeroed elements. There can't be a cluster that starts in daddr 0 so
+ * there can't be an inode #1 either.
+ */
+#define LINKED_AGINO (0x1)
+
+/*
* Record a forwards unlinked chain pointer from agino -> next_agino in our
* staging information.
*/
@@ -1034,31 +1041,40 @@ xrep_iunlink_next(
* the chain or if we should stop walking the chain due to corruption; or a
* per-AG inode number.
*/
-STATIC xfs_agino_t
+STATIC int
xrep_iunlink_reload_next(
struct xrep_agi *ragi,
xfs_agino_t prev_agino,
- xfs_agino_t agino)
+ xfs_agino_t agino,
+ xfs_agino_t *next_agino)
{
struct xfs_scrub *sc = ragi->sc;
struct xfs_inode *ip;
- xfs_agino_t ret = NULLAGINO;
int error;
+ *next_agino = NULLAGINO;
+
error = xchk_iget(ragi->sc, xfs_agino_to_ino(sc->sa.pag, agino), &ip);
if (error)
- return ret;
+ return 0;
trace_xrep_iunlink_reload_next(ip, prev_agino);
/* If this is a linked inode, stop processing the chain. */
if (VFS_I(ip)->i_nlink != 0) {
- xrep_iunlink_store_next(ragi, agino, NULLAGINO);
+ error = xrep_iunlink_store_next(ragi, agino, NULLAGINO);
+ if (error)
+ return error;
+
+ error = xrep_iunlink_store_prev(ragi, agino, LINKED_AGINO);
+ if (error)
+ return error;
+
goto rele;
}
ip->i_prev_unlinked = prev_agino;
- ret = ip->i_next_unlinked;
+ *next_agino = ip->i_next_unlinked;
/*
* Drop the inode reference that we just took. We hold the AGI, so
@@ -1067,7 +1083,7 @@ xrep_iunlink_reload_next(
*/
rele:
xchk_irele(sc, ip);
- return ret;
+ return 0;
}
/*
@@ -1080,18 +1096,22 @@ xrep_iunlink_walk_ondisk_bucket(
struct xrep_agi *ragi,
unsigned int bucket)
{
+ struct xagino_bitmap seen;
struct xfs_scrub *sc = ragi->sc;
- struct xfs_agi *agi = sc->sa.agi_bp->b_addr;
+ struct xfs_agi *agi = ragi->agi_bp->b_addr;
xfs_agino_t prev_agino = NULLAGINO;
xfs_agino_t next_agino;
int error = 0;
+ xagino_bitmap_init(&seen);
+
next_agino = be32_to_cpu(agi->agi_unlinked[bucket]);
while (next_agino != NULLAGINO) {
xfs_agino_t agino = next_agino;
+ unsigned int len = 1;
if (xchk_should_terminate(ragi->sc, &error))
- return error;
+ goto out_bitmap;
trace_xrep_iunlink_walk_ondisk_bucket(sc->sa.pag, bucket,
prev_agino, agino);
@@ -1099,15 +1119,27 @@ xrep_iunlink_walk_ondisk_bucket(
if (bucket != agino % XFS_AGI_UNLINKED_BUCKETS)
break;
+ if (xagino_bitmap_test(&seen, agino, &len))
+ break;
+
next_agino = xrep_iunlink_next(sc, agino);
- if (!next_agino)
- next_agino = xrep_iunlink_reload_next(ragi, prev_agino,
- agino);
+ if (!next_agino) {
+ error = xrep_iunlink_reload_next(ragi, prev_agino,
+ agino, &next_agino);
+ if (error)
+ break;
+ }
+
+ error = xagino_bitmap_set(&seen, agino, 1);
+ if (error)
+ goto out_bitmap;
prev_agino = agino;
}
- return 0;
+out_bitmap:
+ xagino_bitmap_destroy(&seen);
+ return error;
}
/* Decide if this is an unlinked inode in this AG. */
@@ -1296,7 +1328,7 @@ xrep_iunlink_mark_ondisk_rec(
* iunlink_bmp. We haven't checked the inobt yet, so we don't error out if
* the btree is corrupt.
*/
-STATIC void
+STATIC int
xrep_iunlink_mark_ondisk(
struct xrep_agi *ragi)
{
@@ -1308,6 +1340,14 @@ xrep_iunlink_mark_ondisk(
cur = xfs_inobt_init_cursor(sc->sa.pag, sc->tp, agi_bp);
error = xfs_btree_query_all(cur, xrep_iunlink_mark_ondisk_rec, ragi);
xfs_btree_del_cursor(cur, error);
+
+ /*
+ * Don't proceed if we couldn't set a bit in the bitmap. All other
+ * errors we ignore because we haven't actually checked the inobt yet.
+ */
+ if (error == -ENOMEM)
+ return -ENOMEM;
+ return 0;
}
/*
@@ -1320,15 +1360,32 @@ xrep_iunlink_resolve_bucket(
struct xrep_agi *ragi,
unsigned int bucket)
{
+ struct xagino_bitmap seen;
struct xfs_scrub *sc = ragi->sc;
struct xfs_inode *ip;
xfs_agino_t prev_agino = NULLAGINO;
xfs_agino_t next_agino = ragi->iunlink_heads[bucket];
int error = 0;
+ xagino_bitmap_init(&seen);
+
while (next_agino != NULLAGINO) {
+ unsigned int len = 1;
+
if (xchk_should_terminate(ragi->sc, &error))
- return error;
+ goto out_bitmap;
+
+ /* Inode already seen? We're stuck in a loop */
+ if (xagino_bitmap_test(&seen, next_agino, &len)) {
+ trace_xrep_iunlink_resolve_infinite_loop(sc->sa.pag,
+ bucket, prev_agino, next_agino);
+ next_agino = NULLAGINO;
+ break;
+ }
+
+ error = xagino_bitmap_set(&seen, next_agino, 1);
+ if (error)
+ goto out_bitmap;
/* Find the next inode in the chain. */
ip = xfs_iunlink_lookup(sc->sa.pag, next_agino);
@@ -1341,6 +1398,35 @@ xrep_iunlink_resolve_bucket(
break;
}
+ if (VFS_I(ip)->i_nlink != 0) {
+ /*
+ * Inode is linked somewhere! Blow out both unlinked
+ * list pointers, advance the list, and pretend we
+ * didn't see this inode. Clear it from iunlink_bmp
+ * because it's linked.
+ */
+ trace_xrep_iunlink_resolve_allocated(sc->sa.pag,
+ bucket, prev_agino, next_agino);
+
+ error = xrep_iunlink_store_next(ragi, next_agino,
+ NULLAGINO);
+ if (error)
+ goto out_bitmap;
+
+ error = xrep_iunlink_store_prev(ragi, next_agino,
+ LINKED_AGINO);
+ if (error)
+ goto out_bitmap;
+
+ error = xagino_bitmap_clear(&ragi->iunlink_bmp,
+ next_agino, 1);
+ if (error)
+ goto out_bitmap;
+
+ next_agino = ip->i_next_unlinked;
+ continue;
+ }
+
if (next_agino % XFS_AGI_UNLINKED_BUCKETS != bucket) {
/*
* Inode is in the wrong bucket. Advance the list,
@@ -1376,20 +1462,20 @@ xrep_iunlink_resolve_bucket(
*/
error = xagino_bitmap_clear(&ragi->iunlink_bmp, next_agino, 1);
if (error)
- return error;
+ goto out_bitmap;
/* Remember the previous inode's next pointer. */
if (prev_agino != NULLAGINO) {
error = xrep_iunlink_store_next(ragi, prev_agino,
next_agino);
if (error)
- return error;
+ goto out_bitmap;
}
/* Remember this inode's previous pointer. */
error = xrep_iunlink_store_prev(ragi, next_agino, prev_agino);
if (error)
- return error;
+ goto out_bitmap;
/* Advance the list and remember this inode. */
prev_agino = next_agino;
@@ -1400,10 +1486,12 @@ xrep_iunlink_resolve_bucket(
if (prev_agino != NULLAGINO) {
error = xrep_iunlink_store_next(ragi, prev_agino, next_agino);
if (error)
- return error;
+ goto out_bitmap;
}
- return 0;
+out_bitmap:
+ xagino_bitmap_destroy(&seen);
+ return error;
}
/* Reinsert this unlinked inode into the head of the staged bucket list. */
@@ -1428,6 +1516,10 @@ xrep_iunlink_add_to_bucket(
if (error)
return error;
+ error = xrep_iunlink_store_prev(ragi, agino, NULLAGINO);
+ if (error)
+ return error;
+
/* Remember the head inode's previous pointer. */
if (current_head != NULLAGINO) {
error = xrep_iunlink_store_prev(ragi, current_head, agino);
@@ -1495,7 +1587,9 @@ xrep_iunlink_rebuild_buckets(
* If there are ondisk inodes that are unlinked and are not been loaded
* into cache, record them in iunlink_bmp.
*/
- xrep_iunlink_mark_ondisk(ragi);
+ error = xrep_iunlink_mark_ondisk(ragi);
+ if (error)
+ return error;
/*
* Walk each iunlink bucket to (re)construct as much of the incore list
@@ -1517,6 +1611,24 @@ xrep_iunlink_rebuild_buckets(
xrep_iunlink_add_lost_inodes, ragi);
}
+static inline void
+set_inode_prev_unlinked(
+ struct xfs_inode *ip,
+ xfs_agino_t prev_agino)
+{
+ /*
+ * Magic value that means "not unlinked" because xfarrays don't support
+ * storing totally zeroed elements.
+ */
+ if (prev_agino == LINKED_AGINO)
+ prev_agino = 0;
+
+ if (ip->i_prev_unlinked != prev_agino) {
+ trace_xrep_iunlink_relink_prev(ip, prev_agino);
+ ip->i_prev_unlinked = prev_agino;
+ }
+}
+
/* Update i_next_iunlinked for the inode @agino. */
STATIC int
xrep_iunlink_relink_next(
@@ -1550,8 +1662,7 @@ xrep_iunlink_relink_next(
if (error)
goto out_rele;
- trace_xrep_iunlink_relink_prev(ip, prev_agino);
- ip->i_prev_unlinked = prev_agino;
+ set_inode_prev_unlinked(ip, prev_agino);
}
/* Update the forward pointer. */
@@ -1606,7 +1717,7 @@ xrep_iunlink_relink_prev(
want_rele = true;
/* Set the forward pointer since this just came off disk. */
- error = xfarray_load(ragi->iunlink_prev, agino, &next_agino);
+ error = xfarray_load(ragi->iunlink_next, agino, &next_agino);
if (error)
goto out_rele;
@@ -1618,11 +1729,7 @@ xrep_iunlink_relink_prev(
ip->i_next_unlinked = next_agino;
}
- /* Update the backward pointer. */
- if (ip->i_prev_unlinked != prev_agino) {
- trace_xrep_iunlink_relink_prev(ip, prev_agino);
- ip->i_prev_unlinked = prev_agino;
- }
+ set_inode_prev_unlinked(ip, prev_agino);
out_rele:
/*
@@ -1652,6 +1759,8 @@ xrep_iunlink_commit(
if (error)
return error;
}
+ if (error < 0)
+ return error;
/* Fix all the back links */
idx = XFARRAY_CURSOR_INIT;
@@ -1660,6 +1769,8 @@ xrep_iunlink_commit(
if (error)
return error;
}
+ if (error < 0)
+ return error;
/* Copy the staged iunlink buckets to the new AGI. */
for (i = 0; i < XFS_AGI_UNLINKED_BUCKETS; i++) {
diff --git a/fs/xfs/scrub/alloc.c b/fs/xfs/scrub/alloc.c
index 48edaa2cb1e0..c666be69f164 100644
--- a/fs/xfs/scrub/alloc.c
+++ b/fs/xfs/scrub/alloc.c
@@ -136,7 +136,7 @@ xchk_allocbt_rec(
const union xfs_btree_rec *rec)
{
struct xfs_alloc_rec_incore irec;
- struct xchk_alloc *ca = bs->private;
+ struct xchk_alloc *ca = bs->private;
xfs_alloc_btrec_to_irec(rec, &irec);
if (xfs_alloc_check_irec(to_perag(bs->cur->bc_group), &irec) != NULL) {
@@ -144,7 +144,8 @@ xchk_allocbt_rec(
return 0;
}
- xchk_allocbt_mergeable(bs, ca, &irec);
+ if (bs->sc->sm->sm_type == XFS_SCRUB_TYPE_BNOBT)
+ xchk_allocbt_mergeable(bs, ca, &irec);
xchk_allocbt_xref(bs->sc, &irec);
return 0;
diff --git a/fs/xfs/scrub/attr_repair.c b/fs/xfs/scrub/attr_repair.c
index be627ab655ad..6e6af142f1fb 100644
--- a/fs/xfs/scrub/attr_repair.c
+++ b/fs/xfs/scrub/attr_repair.c
@@ -1427,7 +1427,8 @@ xrep_xattr_rebuild_tree(
* If we didn't find any attributes to salvage, repair the file by
* zapping its attr fork.
*/
- if (rx->attrs_found == 0) {
+ if (rx->attrs_found == 0 &&
+ (!xfs_has_parent(sc->mp) || xfarray_length(rx->pptr_recs) == 0)) {
xfs_trans_ijoin(sc->tp, sc->ip, 0);
error = xrep_xattr_reset_fork(sc);
if (error)
diff --git a/fs/xfs/scrub/bmap.c b/fs/xfs/scrub/bmap.c
index 70028da1aacc..401c278725d2 100644
--- a/fs/xfs/scrub/bmap.c
+++ b/fs/xfs/scrub/bmap.c
@@ -1170,6 +1170,11 @@ xchk_bmap_attr(
}
error = xchk_bmap(sc, XFS_ATTR_FORK);
+ /* A repaired, empty attr fork no longer has mappings to check. */
+ if (error == -ENOENT && (sc->flags & XREP_ALREADY_FIXED)) {
+ xchk_mark_healthy_if_clean(sc, XFS_SICK_INO_BMBTA_ZAPPED);
+ return 0;
+ }
if (error)
return error;
diff --git a/fs/xfs/scrub/dirtree_repair.c b/fs/xfs/scrub/dirtree_repair.c
index 1c0d7ea4a5be..bbf6acf6fd40 100644
--- a/fs/xfs/scrub/dirtree_repair.c
+++ b/fs/xfs/scrub/dirtree_repair.c
@@ -349,6 +349,8 @@ xrep_dirtree_unlink_iolock(
ASSERT(sc->ilock_flags & XFS_IOLOCK_EXCL);
+ if (sc->ip == dp)
+ return 0;
if (xfs_ilock_nowait(dp, XFS_IOLOCK_EXCL))
return 0;
@@ -400,8 +402,18 @@ xrep_dirtree_unlink(
* directory code can handle a reservationless update.
*/
resblks = xfs_remove_space_res(mp, step->name_len);
- error = xfs_trans_alloc_dir(dp, &M_RES(mp)->tr_remove, sc->ip,
- &resblks, &sc->tp, &dontcare);
+ if (sc->ip == dp) {
+again:
+ error = xfs_trans_alloc_inode(dp, &M_RES(mp)->tr_remove,
+ resblks, 0, false, &sc->tp);
+ if ((error == -ENOSPC || error == -EDQUOT) && resblks > 0) {
+ resblks = 0;
+ goto again;
+ }
+ } else {
+ error = xfs_trans_alloc_dir(dp, &M_RES(mp)->tr_remove, sc->ip,
+ &resblks, &sc->tp, &dontcare);
+ }
if (error)
goto out_iolock;
@@ -489,9 +501,11 @@ out_trans_cancel:
xchk_trans_cancel(sc);
out_ilock:
xfs_iunlock(sc->ip, XFS_ILOCK_EXCL);
- xfs_iunlock(dp, XFS_ILOCK_EXCL);
+ if (dp != sc->ip)
+ xfs_iunlock(dp, XFS_ILOCK_EXCL);
out_iolock:
- xfs_iunlock(dp, XFS_IOLOCK_EXCL);
+ if (dp != sc->ip)
+ xfs_iunlock(dp, XFS_IOLOCK_EXCL);
return error;
}
diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c
index 3ec41c198351..8bc508336aa5 100644
--- a/fs/xfs/scrub/inode_repair.c
+++ b/fs/xfs/scrub/inode_repair.c
@@ -1797,7 +1797,7 @@ xrep_inode_flags(
/* Clear junk flags */
if (sc->ip->i_diflags & ~XFS_DIFLAG_ANY)
- sc->ip->i_diflags &= ~XFS_DIFLAG_ANY;
+ sc->ip->i_diflags &= XFS_DIFLAG_ANY;
/* NEWRTBM only applies to realtime bitmaps */
if (I_INO(sc->ip) == sc->mp->m_sb.sb_rbmino)
@@ -1828,7 +1828,7 @@ xrep_inode_flags(
/* Clear junk flags. */
if (sc->ip->i_diflags2 & ~XFS_DIFLAG2_ANY)
- sc->ip->i_diflags2 &= ~XFS_DIFLAG2_ANY;
+ sc->ip->i_diflags2 &= XFS_DIFLAG2_ANY;
/* No reflink flag unless we support it and it's a file. */
if (!xfs_has_reflink(sc->mp) || !S_ISREG(mode))
@@ -1960,7 +1960,7 @@ xrep_inode_cowextsize(
/* Fix misaligned CoW extent size hints on a directory. */
if ((sc->ip->i_diflags & XFS_DIFLAG_RTINHERIT) &&
(sc->ip->i_diflags2 & XFS_DIFLAG2_COWEXTSIZE) &&
- sc->ip->i_extsize % sc->mp->m_sb.sb_rextsize > 0) {
+ xfs_extlen_to_rtxmod(sc->mp, sc->ip->i_cowextsize) > 0) {
sc->ip->i_cowextsize = 0;
sc->ip->i_diflags2 &= ~XFS_DIFLAG2_COWEXTSIZE;
}
diff --git a/fs/xfs/scrub/nlinks.c b/fs/xfs/scrub/nlinks.c
index 355ab6de23ea..bcedb8c3e4e6 100644
--- a/fs/xfs/scrub/nlinks.c
+++ b/fs/xfs/scrub/nlinks.c
@@ -383,6 +383,12 @@ xchk_nlinks_ilock_dir(
uint lock_mode = XFS_ILOCK_SHARED;
/*
+ * Take the IOLOCK so that other threads cannot start a directory
+ * update while we're scanning.
+ */
+ xfs_ilock(ip, XFS_IOLOCK_SHARED);
+
+ /*
* We're going to scan the directory entries, so we must be ready to
* pull the data fork mappings into memory if they aren't already.
*/
@@ -397,13 +403,8 @@ xchk_nlinks_ilock_dir(
xfs_need_iread_extents(&ip->i_af))
lock_mode = XFS_ILOCK_EXCL;
- /*
- * Take the IOLOCK so that other threads cannot start a directory
- * update while we're scanning.
- */
- lock_mode |= XFS_IOLOCK_SHARED;
xfs_ilock(ip, lock_mode);
- return lock_mode;
+ return lock_mode | XFS_IOLOCK_SHARED;
}
/* Walk a directory to bump the observed link counts of the children. */
diff --git a/fs/xfs/scrub/nlinks_repair.c b/fs/xfs/scrub/nlinks_repair.c
index fbc2ff809fc0..09e097e16689 100644
--- a/fs/xfs/scrub/nlinks_repair.c
+++ b/fs/xfs/scrub/nlinks_repair.c
@@ -232,9 +232,14 @@ xrep_nlinks_repair_inode(
* unlinked list, put it on the unlinked list.
*/
if (total_links == 0 && !xfs_inode_on_unlinked_list(ip)) {
+ if (actual_nlink)
+ clear_nlink(VFS_I(ip));
error = xfs_iunlink(sc->tp, ip);
- if (error)
+ if (error) {
+ if (actual_nlink)
+ set_nlink(VFS_I(ip), actual_nlink);
goto out_trans;
+ }
dirty = true;
}
diff --git a/fs/xfs/scrub/parent.c b/fs/xfs/scrub/parent.c
index a8c4807e1d94..99b60773b715 100644
--- a/fs/xfs/scrub/parent.c
+++ b/fs/xfs/scrub/parent.c
@@ -485,7 +485,7 @@ xchk_parent_scan_attr(
valuelen, &parent_ino, NULL);
if (error) {
xchk_fblock_set_corrupt(sc, XFS_ATTR_FORK, 0);
- return error;
+ return -ECANCELED;
}
/* No self-referential parent pointers. */
diff --git a/fs/xfs/scrub/rtbitmap_repair.c b/fs/xfs/scrub/rtbitmap_repair.c
index dc64902d6c25..442a17bf9720 100644
--- a/fs/xfs/scrub/rtbitmap_repair.c
+++ b/fs/xfs/scrub/rtbitmap_repair.c
@@ -36,6 +36,24 @@
/* rt bitmap content repairs */
+/*
+ * Reserve enough blocks to write out a completely new bitmap file, plus twice
+ * as many blocks as we would need if we can only allocate one block per data
+ * fork mapping. This should cover the preallocation of the temporary file and
+ * exchanging the extent mappings.
+ *
+ * We cannot use xfs_exchmaps_estimate because we have not yet constructed the
+ * replacement bitmap and therefore do not know how many extents it will use.
+ * By the time we do, we will have a dirty transaction (which we cannot drop
+ * because we cannot drop the rtbitmap ILOCK) and cannot ask for more
+ * reservation.
+ */
+static inline unsigned long long
+xrep_rtbitmap_calc_blocks(struct xfs_mount *mp, unsigned long long blocks)
+{
+ return blocks + (xfs_bmbt_calc_size(mp, blocks) * 2);
+}
+
/* Set up to repair the realtime bitmap for this group. */
int
xrep_setup_rtbitmap(
@@ -56,20 +74,7 @@ xrep_setup_rtbitmap(
if (error)
return error;
- /*
- * Reserve enough blocks to write out a completely new bitmap file,
- * plus twice as many blocks as we would need if we can only allocate
- * one block per data fork mapping. This should cover the
- * preallocation of the temporary file and exchanging the extent
- * mappings.
- *
- * We cannot use xfs_exchmaps_estimate because we have not yet
- * constructed the replacement bitmap and therefore do not know how
- * many extents it will use. By the time we do, we will have a dirty
- * transaction (which we cannot drop because we cannot drop the
- * rtbitmap ILOCK) and cannot ask for more reservation.
- */
- blocks += xfs_bmbt_calc_size(mp, blocks) * 2;
+ blocks = xrep_rtbitmap_calc_blocks(mp, mp->m_sb.sb_rbmblocks);
if (blocks > UINT_MAX)
return -EOPNOTSUPP;
@@ -512,7 +517,7 @@ xrep_rtbitmap(
struct xchk_rtbitmap *rtb = sc->buf;
struct xfs_mount *mp = sc->mp;
struct xfs_group *xg = rtg_group(sc->sr.rtg);
- unsigned long long blocks = 0;
+ unsigned long long blocks;
unsigned int busy_gen;
int error;
@@ -532,15 +537,20 @@ xrep_rtbitmap(
* figure out if we need to adjust the block reservation in the
* transaction.
*/
- blocks = xfs_bmbt_calc_size(mp, rtb->rbmblocks);
+ blocks = xrep_rtbitmap_calc_blocks(mp, rtb->rbmblocks);
if (blocks > UINT_MAX)
return -EOPNOTSUPP;
if (blocks > rtb->resblks) {
- error = xfs_trans_reserve_more(sc->tp, blocks, 0);
+ uint64_t delta = blocks - rtb->resblks;
+
+ if (delta > UINT_MAX)
+ return -EOPNOTSUPP;
+
+ error = xfs_trans_reserve_more(sc->tp, delta, 0);
if (error)
return error;
- rtb->resblks += blocks;
+ rtb->resblks += delta;
}
/* Fix inode core and forks. */
diff --git a/fs/xfs/scrub/rtsummary.c b/fs/xfs/scrub/rtsummary.c
index 78f72a046887..546b335ade13 100644
--- a/fs/xfs/scrub/rtsummary.c
+++ b/fs/xfs/scrub/rtsummary.c
@@ -358,7 +358,7 @@ xchk_rtsummary(
* EFSCORRUPTED means the rtbitmap is corrupt, which is an xref
* error since we're checking the summary file.
*/
- xchk_ip_set_corrupt(sc, rbmip);
+ xchk_ip_xref_set_corrupt(sc, rbmip);
return 0;
}
if (error)
diff --git a/fs/xfs/scrub/tempfile.c b/fs/xfs/scrub/tempfile.c
index e0c630f888cf..98820003b929 100644
--- a/fs/xfs/scrub/tempfile.c
+++ b/fs/xfs/scrub/tempfile.c
@@ -174,6 +174,7 @@ out_release_inode:
xfs_iunlock(sc->tempip, XFS_ILOCK_EXCL);
xfs_finish_inode_setup(sc->tempip);
xchk_irele(sc, sc->tempip);
+ sc->tempip = NULL;
}
out_release_dquots:
xfs_qm_dqrele(udqp);
diff --git a/fs/xfs/scrub/trace.h b/fs/xfs/scrub/trace.h
index d5d39d82749e..14aa0ec1f09e 100644
--- a/fs/xfs/scrub/trace.h
+++ b/fs/xfs/scrub/trace.h
@@ -3538,10 +3538,12 @@ DEFINE_EVENT(xrep_iunlink_resolve_class, name, \
TP_PROTO(const struct xfs_perag *pag, unsigned int bucket, \
xfs_agino_t prev_agino, xfs_agino_t next_agino), \
TP_ARGS(pag, bucket, prev_agino, next_agino))
+DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_infinite_loop);
DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_uncached);
DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_wronglist);
DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_nolist);
DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_ok);
+DEFINE_REPAIR_IUNLINK_RESOLVE_EVENT(xrep_iunlink_resolve_allocated);
TRACE_EVENT(xrep_iunlink_relink_next,
TP_PROTO(struct xfs_inode *ip, xfs_agino_t next_agino),
diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
index e1465e950acc..48d7dfd3e15f 100644
--- a/fs/xfs/xfs_buf.c
+++ b/fs/xfs/xfs_buf.c
@@ -114,7 +114,7 @@ xfs_buf_free(
vfree(bp->b_addr);
else if (bp->b_flags & _XBF_KMEM)
kfree(bp->b_addr);
- else
+ else if (bp->b_addr)
folio_put(virt_to_folio(bp->b_addr));
call_rcu(&bp->b_rcu, xfs_buf_free_callback);
diff --git a/fs/xfs/xfs_buf_item_recover.c b/fs/xfs/xfs_buf_item_recover.c
index 02b95b89d1b5..240deb3f7827 100644
--- a/fs/xfs/xfs_buf_item_recover.c
+++ b/fs/xfs/xfs_buf_item_recover.c
@@ -461,7 +461,7 @@ xlog_recover_validate_buf_type(
* given buffer. The bitmap in the buf log format structure indicates
* where to place the logged data.
*/
-STATIC void
+STATIC int
xlog_recover_do_reg_buffer(
struct xfs_mount *mp,
struct xlog_recover_item *item,
@@ -489,8 +489,24 @@ xlog_recover_do_reg_buffer(
ASSERT(nbits > 0);
ASSERT(item->ri_buf[i].iov_base != NULL);
ASSERT(item->ri_buf[i].iov_len % XFS_BLF_CHUNK == 0);
- ASSERT(BBTOB(bp->b_length) >=
- ((uint)bit << XFS_BLF_SHIFT) + (nbits << XFS_BLF_SHIFT));
+ /*
+ * The bitmap is only trustworthy to the extent that it
+ * describes a region that actually fits inside the buffer we
+ * read in based on the (attacker-controlled) blf_len. Do not
+ * rely on an ASSERT() for this -- it compiles away entirely on
+ * non-DEBUG kernels, which is exactly where this matters, so
+ * validate it for real and abort recovery of this buffer rather
+ * than copying past the end of it.
+ */
+ if (XFS_IS_CORRUPT(mp, BBTOB(bp->b_length) <
+ ((uint)bit << XFS_BLF_SHIFT) +
+ (nbits << XFS_BLF_SHIFT))) {
+ xfs_alert(mp,
+ "Bad buffer log item dirty bitmap (bit %d, nbits %d) for %d-byte buffer at daddr 0x%llx.",
+ bit, nbits, BBTOB(bp->b_length),
+ xfs_buf_daddr(bp));
+ return -EFSCORRUPTED;
+ }
/*
* The dirty regions logged in the buffer, even though
@@ -544,6 +560,7 @@ xlog_recover_do_reg_buffer(
ASSERT(i == item->ri_total);
xlog_recover_validate_buf_type(mp, bp, buf_f, current_lsn);
+ return 0;
}
/*
@@ -552,10 +569,10 @@ xlog_recover_do_reg_buffer(
* (ie. USR or GRP), then just toss this buffer away; don't recover it.
* Else, treat it as a regular buffer and do recovery.
*
- * Return false if the buffer was tossed and true if we recovered the buffer to
- * indicate to the caller if the buffer needs writing.
+ * Return 0 if the buffer was not recovered (tossed), 1 if it was recovered and
+ * needs writing, or a negative errno if recovery of the buffer failed.
*/
-STATIC bool
+STATIC int
xlog_recover_do_dquot_buffer(
struct xfs_mount *mp,
struct xlog *log,
@@ -564,6 +581,7 @@ xlog_recover_do_dquot_buffer(
struct xfs_buf_log_format *buf_f)
{
uint type;
+ int error;
trace_xfs_log_recover_buf_dquot_buf(log, buf_f);
@@ -571,7 +589,7 @@ xlog_recover_do_dquot_buffer(
* Filesystems are required to send in quota flags at mount time.
*/
if (!mp->m_qflags)
- return false;
+ return 0;
type = 0;
if (buf_f->blf_flags & XFS_BLF_UDQUOT_BUF)
@@ -584,10 +602,12 @@ xlog_recover_do_dquot_buffer(
* This type of quotas was turned off, so ignore this buffer
*/
if (log->l_quotaoffs_flag & type)
- return false;
+ return 0;
- xlog_recover_do_reg_buffer(mp, item, bp, buf_f, NULLCOMMITLSN);
- return true;
+ error = xlog_recover_do_reg_buffer(mp, item, bp, buf_f, NULLCOMMITLSN);
+ if (error)
+ return error;
+ return 1;
}
/*
@@ -724,7 +744,9 @@ xlog_recover_do_primary_sb_buffer(
xfs_rgnumber_t orig_rgcount = mp->m_sb.sb_rgcount;
int error;
- xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn);
+ error = xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn);
+ if (error)
+ return error;
if (orig_agcount == 0) {
xfs_alert(mp, "Trying to grow file system without AGs");
@@ -1081,11 +1103,11 @@ xlog_recover_buf_commit_pass2(
goto out_release;
} else if (buf_f->blf_flags &
(XFS_BLF_UDQUOT_BUF|XFS_BLF_PDQUOT_BUF|XFS_BLF_GDQUOT_BUF)) {
- bool dirty;
-
- dirty = xlog_recover_do_dquot_buffer(mp, log, item, bp, buf_f);
- if (!dirty)
+ error = xlog_recover_do_dquot_buffer(mp, log, item, bp, buf_f);
+ if (error <= 0)
goto out_release;
+ /* write dirty buffer */
+ error = 0;
} else if ((xfs_blft_from_flags(buf_f) & XFS_BLFT_SB_BUF) &&
xfs_buf_daddr(bp) == 0) {
error = xlog_recover_do_primary_sb_buffer(mp, item, bp, buf_f,
@@ -1105,7 +1127,10 @@ xlog_recover_buf_commit_pass2(
xfs_buf_relse(rtsb_bp);
}
} else {
- xlog_recover_do_reg_buffer(mp, item, bp, buf_f, current_lsn);
+ error = xlog_recover_do_reg_buffer(mp, item, bp, buf_f,
+ current_lsn);
+ if (error)
+ goto out_release;
}
/*
diff --git a/fs/xfs/xfs_dquot.c b/fs/xfs/xfs_dquot.c
index c311f61d9554..b4f6c594808c 100644
--- a/fs/xfs/xfs_dquot.c
+++ b/fs/xfs/xfs_dquot.c
@@ -778,7 +778,7 @@ xfs_dq_get_next_id(
lock_flags = xfs_ilock_data_map_shared(quotip);
error = xfs_iread_extents(NULL, quotip, XFS_DATA_FORK);
if (error)
- return error;
+ goto out_unlock;
if (xfs_iext_lookup_extent(quotip, &quotip->i_df, start, &cur, &got)) {
/* contiguous chunk, bump startoff for the id calculation */
@@ -789,6 +789,7 @@ xfs_dq_get_next_id(
error = -ENOENT;
}
+out_unlock:
xfs_iunlock(quotip, lock_flags);
return error;
diff --git a/fs/xfs/xfs_dquot_item_recover.c b/fs/xfs/xfs_dquot_item_recover.c
index fe419b28de22..63bc9ab7d947 100644
--- a/fs/xfs/xfs_dquot_item_recover.c
+++ b/fs/xfs/xfs_dquot_item_recover.c
@@ -173,7 +173,7 @@ xlog_recover_dquot_commit_pass2(
out_release:
xfs_buf_relse(bp);
- return 0;
+ return error;
}
const struct xlog_recover_item_ops xlog_dquot_item_ops = {
diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h
index 66a02d1b9ad7..216a38a354e7 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -349,6 +349,13 @@ typedef struct xfs_mount {
/* Index of uuid record in the uuid xarray. */
unsigned int m_uuid_table_index;
+
+ /*
+ * Old io_pages/ra_pages valued in the main bdev BDI, and our initial
+ * calculated values.
+ */
+ unsigned long m_old_io_pages, m_initial_io_pages;
+ unsigned long m_old_ra_pages, m_initial_ra_pages;
} xfs_mount_t;
#define M_IGEO(mp) (&(mp)->m_ino_geo)
diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c
index 7a3f97686989..84efe5a8fb11 100644
--- a/fs/xfs/xfs_rtalloc.c
+++ b/fs/xfs/xfs_rtalloc.c
@@ -737,7 +737,7 @@ xfs_rtginode_ensure(
xfs_trans_cancel(tp);
if (error != -ENOENT)
- return 0;
+ return error;
return xfs_rtginode_create(rtg, type, true);
}
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index 8531d526fc44..63c4bcbe6c2b 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -549,6 +549,52 @@ xfs_open_devices(
}
/*
+ * When using a RT device some or all data I/O is using the RT device, but
+ * the BDI is inherited from the main data device. When the underlying block
+ * device for the RT device has larger I/O sizes, the BDI settings might be
+ * incorrect, which is especially bad if the main device is a SSD and the
+ * RT device is a HDD, as the io_opt fixup in blk_apply_bdi_limits is missing
+ * for this case.
+ *
+ * Update the BDI values to the max of the data and RT device to cover our
+ * bases.
+ */
+static void
+xfs_update_bdi_rahead(
+ struct xfs_mount *mp)
+{
+ struct backing_dev_info *rt_bdi =
+ mp->m_rtdev_targp->bt_bdev->bd_disk->bdi;
+ struct backing_dev_info *sb_bdi = mp->m_super->s_bdi;
+
+ mp->m_old_io_pages = sb_bdi->io_pages;
+ mp->m_old_ra_pages = sb_bdi->ra_pages;
+
+ sb_bdi->io_pages = mp->m_initial_io_pages =
+ max(sb_bdi->io_pages, rt_bdi->io_pages);
+ sb_bdi->ra_pages = mp->m_initial_ra_pages =
+ max(sb_bdi->ra_pages, rt_bdi->ra_pages);
+}
+
+static void
+xfs_restore_bdi_rahead(
+ struct xfs_mount *mp)
+{
+ struct backing_dev_info *sb_bdi = mp->m_super->s_bdi;
+
+ if (sb_bdi->io_pages == mp->m_initial_io_pages)
+ sb_bdi->io_pages = mp->m_old_io_pages;
+ else
+ xfs_info(mp, "io_pages changed from %lu to %lu, not restoring.",
+ mp->m_initial_io_pages, sb_bdi->io_pages);
+ if (sb_bdi->ra_pages == mp->m_initial_ra_pages)
+ sb_bdi->ra_pages = mp->m_old_ra_pages;
+ else
+ xfs_info(mp, "ra_pages changed from %lu to %lu, not restoring.",
+ mp->m_initial_ra_pages, sb_bdi->ra_pages);
+}
+
+/*
* Setup xfs_mount buffer target pointers based on superblock
*/
STATIC int
@@ -585,6 +631,7 @@ xfs_setup_devices(
mp->m_sb.sb_sectsize, mp->m_sb.sb_rblocks);
if (error)
return error;
+ xfs_update_bdi_rahead(mp);
}
return 0;
@@ -2283,8 +2330,12 @@ static void
xfs_kill_sb(
struct super_block *sb)
{
+ struct xfs_mount *mp = XFS_M(sb);
+
+ if (mp->m_rtdev_targp && mp->m_rtdev_targp != mp->m_ddev_targp)
+ xfs_restore_bdi_rahead(mp);
kill_block_super(sb);
- xfs_mount_free(XFS_M(sb));
+ xfs_mount_free(mp);
}
static struct file_system_type xfs_fs_type = {
diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c
index f76a09130852..7ab8f2218c6a 100644
--- a/fs/xfs/xfs_zone_gc.c
+++ b/fs/xfs/xfs_zone_gc.c
@@ -103,6 +103,7 @@ struct xfs_gc_bio {
/* Open Zone being written to */
struct xfs_open_zone *oz;
+ /* Realtime group currently being reclaimed */
struct xfs_rtgroup *victim_rtg;
/* Bio used for reads and writes, including the bvec used by it */
@@ -130,6 +131,9 @@ struct xfs_zone_gc_data {
/* bioset used to allocate the gc_bios */
struct bio_set bio_set;
+ /* bioset used when writes need to be split to hardware limits */
+ struct bio_set split_bio_set;
+
/*
* Scratchpad to buffer GC data, organized as a ring buffer over
* discontiguous folios. scratch_head is where the buffer is filled,
@@ -221,6 +225,9 @@ xfs_zone_gc_data_alloc(
if (bioset_init(&data->bio_set, 16, offsetof(struct xfs_gc_bio, bio),
BIOSET_NEED_BVECS))
goto out_free_recs;
+ if (bioset_init(&data->split_bio_set, 16,
+ offsetof(struct xfs_gc_bio, bio), 0))
+ goto out_exit_bio_set;
for (i = 0; i < XFS_GC_NR_BUFS; i++) {
data->scratch_folios[i] =
folio_alloc(GFP_KERNEL, get_order(XFS_GC_BUF_SIZE));
@@ -238,6 +245,8 @@ xfs_zone_gc_data_alloc(
out_free_scratch:
while (--i >= 0)
folio_put(data->scratch_folios[i]);
+ bioset_exit(&data->split_bio_set);
+out_exit_bio_set:
bioset_exit(&data->bio_set);
out_free_recs:
kfree(data->iter.recs);
@@ -254,6 +263,7 @@ xfs_zone_gc_data_free(
for (i = 0; i < XFS_GC_NR_BUFS; i++)
folio_put(data->scratch_folios[i]);
+ bioset_exit(&data->split_bio_set);
bioset_exit(&data->bio_set);
kfree(data->iter.recs);
kfree(data);
@@ -810,7 +820,8 @@ xfs_zone_gc_split_write(
data->mp->m_sb.sb_blocksize) >> SECTOR_SHIFT;
split_len = split_sectors << SECTOR_SHIFT;
- split = bio_split(&chunk->bio, split_sectors, GFP_NOFS, &data->bio_set);
+ split = bio_split(&chunk->bio, split_sectors, GFP_NOFS,
+ &data->split_bio_set);
split_chunk = container_of(split, struct xfs_gc_bio, bio);
split_chunk->data = data;
ihold(VFS_I(chunk->ip));