From 5f36c9ca33336036a087b270e68e8236c733f448 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 26 Mar 2026 10:54:08 +0100 Subject: fs: Rename generic_file_fsync() to simple_fsync() The implementation is now really basic so rename generic_file_fsync() simple_fsync() and __generic_file_fsync() to simple_fsync_noflush(). Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260326095354.16340-56-jack@suse.cz Signed-off-by: Christian Brauner --- include/linux/fs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/fs.h b/include/linux/fs.h index 8b3dd145b25e..0fc0cb23000e 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -3295,8 +3295,8 @@ void simple_offset_destroy(struct offset_ctx *octx); extern const struct file_operations simple_offset_dir_operations; -extern int __generic_file_fsync(struct file *, loff_t, loff_t, int); -extern int generic_file_fsync(struct file *, loff_t, loff_t, int); +extern int simple_fsync_noflush(struct file *, loff_t, loff_t, int); +extern int simple_fsync(struct file *, loff_t, loff_t, int); extern int generic_check_addressable(unsigned, u64); -- cgit v1.2.3 From 972b9dd4e4180fbb2352bf2f0e015b7b63f5cca0 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 26 Mar 2026 10:54:16 +0100 Subject: fs: Ignore inode metadata buffers in inode_lru_isolate() There are only a few filesystems that use generic tracking of inode metadata buffer heads. As such the logic to reclaim tracked metadata buffer heads in inode_lru_isolate() doesn't bring a benefit big enough to justify intertwining of inode reclaim and metadata buffer head tracking. Just treat tracked metadata buffer heads as any other metadata filesystem has to properly clean up on inode eviction and stop handling it in inode_lru_isolate(). As a result filesystems using generic tracking of metadata buffer heads may now see dirty metadata buffers in their .evict methods more often which can slow down inode reclaim but given these filesystems aren't used in performance demanding setups we should be fine. Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260326095354.16340-64-jack@suse.cz Tested-by: syzbot@syzkaller.appspotmail.com Signed-off-by: Christian Brauner --- fs/buffer.c | 29 ----------------------------- fs/inode.c | 21 +++++++++------------ include/linux/buffer_head.h | 3 --- 3 files changed, 9 insertions(+), 44 deletions(-) (limited to 'include') diff --git a/fs/buffer.c b/fs/buffer.c index 1bc0f22f3cc2..bd48644e1bf8 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -878,35 +878,6 @@ void invalidate_inode_buffers(struct inode *inode) } EXPORT_SYMBOL(invalidate_inode_buffers); -/* - * Remove any clean buffers from the inode's buffer list. This is called - * when we're trying to free the inode itself. Those buffers can pin it. - * - * Returns true if all buffers were removed. - */ -int remove_inode_buffers(struct inode *inode) -{ - int ret = 1; - - if (inode_has_buffers(inode)) { - struct address_space *mapping = &inode->i_data; - struct list_head *list = &mapping->i_private_list; - struct address_space *buffer_mapping = mapping->i_private_data; - - spin_lock(&buffer_mapping->i_private_lock); - while (!list_empty(list)) { - struct buffer_head *bh = BH_ENTRY(list->next); - if (buffer_dirty(bh)) { - ret = 0; - break; - } - __remove_assoc_queue(bh); - } - spin_unlock(&buffer_mapping->i_private_lock); - } - return ret; -} - /* * Create the appropriate buffers when given a folio for data area and * the size of each buffer.. Use the bh->b_this_page linked list to diff --git a/fs/inode.c b/fs/inode.c index cc12b68e021b..4f98a5f04bbd 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -17,7 +17,6 @@ #include #include #include -#include /* for inode_has_buffers */ #include #include #include @@ -367,7 +366,6 @@ struct inode *alloc_inode(struct super_block *sb) void __destroy_inode(struct inode *inode) { - BUG_ON(inode_has_buffers(inode)); inode_detach_wb(inode); security_inode_free(inode); fsnotify_inode_delete(inode); @@ -994,19 +992,18 @@ static enum lru_status inode_lru_isolate(struct list_head *item, * page cache in order to free up struct inodes: lowmem might * be under pressure before the cache inside the highmem zone. */ - if (inode_has_buffers(inode) || !mapping_empty(&inode->i_data)) { + if (!mapping_empty(&inode->i_data)) { + unsigned long reap; + inode_pin_lru_isolating(inode); spin_unlock(&inode->i_lock); spin_unlock(&lru->lock); - if (remove_inode_buffers(inode)) { - unsigned long reap; - reap = invalidate_mapping_pages(&inode->i_data, 0, -1); - if (current_is_kswapd()) - __count_vm_events(KSWAPD_INODESTEAL, reap); - else - __count_vm_events(PGINODESTEAL, reap); - mm_account_reclaimed_pages(reap); - } + reap = invalidate_mapping_pages(&inode->i_data, 0, -1); + if (current_is_kswapd()) + __count_vm_events(KSWAPD_INODESTEAL, reap); + else + __count_vm_events(PGINODESTEAL, reap); + mm_account_reclaimed_pages(reap); inode_unpin_lru_isolating(inode); return LRU_RETRY; } diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index b16b88bfbc3e..631bf971efc0 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -517,7 +517,6 @@ void buffer_init(void); bool try_to_free_buffers(struct folio *folio); int inode_has_buffers(struct inode *inode); void invalidate_inode_buffers(struct inode *inode); -int remove_inode_buffers(struct inode *inode); int sync_mapping_buffers(struct address_space *mapping); void invalidate_bh_lrus(void); void invalidate_bh_lrus_cpu(void); @@ -528,9 +527,7 @@ extern int buffer_heads_over_limit; static inline void buffer_init(void) {} static inline bool try_to_free_buffers(struct folio *folio) { return true; } -static inline int inode_has_buffers(struct inode *inode) { return 0; } static inline void invalidate_inode_buffers(struct inode *inode) {} -static inline int remove_inode_buffers(struct inode *inode) { return 1; } static inline int sync_mapping_buffers(struct address_space *mapping) { return 0; } static inline void invalidate_bh_lrus(void) {} static inline void invalidate_bh_lrus_cpu(void) {} -- cgit v1.2.3 From 2811f2a82fafff40867b318360cc06143b088a7c Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 26 Mar 2026 10:54:18 +0100 Subject: hugetlbfs: Stop using i_private_data Instead of using i_private_data for resv_map pointer add the pointer into hugetlbfs private part of the inode. Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260326095354.16340-66-jack@suse.cz Signed-off-by: Christian Brauner --- fs/hugetlbfs/inode.c | 11 +++-------- include/linux/hugetlb.h | 1 + mm/hugetlb.c | 10 +--------- 3 files changed, 5 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 3f70c47981de..6ad02493adfd 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -622,13 +622,7 @@ static void hugetlbfs_evict_inode(struct inode *inode) trace_hugetlbfs_evict_inode(inode); remove_inode_hugepages(inode, 0, LLONG_MAX); - /* - * Get the resv_map from the address space embedded in the inode. - * This is the address space which points to any resv_map allocated - * at inode creation time. If this is a device special inode, - * i_mapping may not point to the original address space. - */ - resv_map = (struct resv_map *)(&inode->i_data)->i_private_data; + resv_map = HUGETLBFS_I(inode)->resv_map; /* Only regular and link inodes have associated reserve maps */ if (resv_map) resv_map_release(&resv_map->refs); @@ -907,6 +901,7 @@ static struct inode *hugetlbfs_get_root(struct super_block *sb, simple_inode_init_ts(inode); inode->i_op = &hugetlbfs_dir_inode_operations; inode->i_fop = &simple_dir_operations; + HUGETLBFS_I(inode)->resv_map = NULL; /* directory inodes start off with i_nlink == 2 (for "." entry) */ inc_nlink(inode); lockdep_annotate_inode_mutex_key(inode); @@ -950,7 +945,7 @@ static struct inode *hugetlbfs_get_inode(struct super_block *sb, &hugetlbfs_i_mmap_rwsem_key); inode->i_mapping->a_ops = &hugetlbfs_aops; simple_inode_init_ts(inode); - inode->i_mapping->i_private_data = resv_map; + info->resv_map = resv_map; info->seals = F_SEAL_SEAL; switch (mode & S_IFMT) { default: diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h index 65910437be1c..fc5462fe943f 100644 --- a/include/linux/hugetlb.h +++ b/include/linux/hugetlb.h @@ -518,6 +518,7 @@ static inline struct hugetlbfs_sb_info *HUGETLBFS_SB(struct super_block *sb) struct hugetlbfs_inode_info { struct inode vfs_inode; + struct resv_map *resv_map; unsigned int seals; }; diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 0beb6e22bc26..7ab5c724a711 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -1157,15 +1157,7 @@ void resv_map_release(struct kref *ref) static inline struct resv_map *inode_resv_map(struct inode *inode) { - /* - * At inode evict time, i_mapping may not point to the original - * address space within the inode. This original address space - * contains the pointer to the resv_map. So, always use the - * address space embedded within the inode. - * The VERY common case is inode->mapping == &inode->i_data but, - * this may not be true for device special inodes. - */ - return (struct resv_map *)(&inode->i_data)->i_private_data; + return HUGETLBFS_I(inode)->resv_map; } static struct resv_map *vma_resv_map(struct vm_area_struct *vma) -- cgit v1.2.3 From cd336f2e275de14866101d3395c7d2be0a0c1b04 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 26 Mar 2026 10:54:20 +0100 Subject: fs: Remove i_private_data Nobody is using it anymore. Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260326095354.16340-68-jack@suse.cz Tested-by: syzbot@syzkaller.appspotmail.com Signed-off-by: Christian Brauner --- fs/inode.c | 1 - include/linux/fs.h | 2 -- 2 files changed, 3 deletions(-) (limited to 'include') diff --git a/fs/inode.c b/fs/inode.c index 4f98a5f04bbd..d5774e627a9c 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -283,7 +283,6 @@ int inode_init_always_gfp(struct super_block *sb, struct inode *inode, gfp_t gfp atomic_set(&mapping->nr_thps, 0); #endif mapping_set_gfp_mask(mapping, GFP_HIGHUSER_MOVABLE); - mapping->i_private_data = NULL; mapping->writeback_index = 0; init_rwsem(&mapping->invalidate_lock); lockdep_set_class_and_name(&mapping->invalidate_lock, diff --git a/include/linux/fs.h b/include/linux/fs.h index 0fc0cb23000e..d488459396f4 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -465,7 +465,6 @@ extern const struct address_space_operations empty_aops; * @wb_err: The most recent error which has occurred. * @i_private_lock: For use by the owner of the address_space. * @i_private_list: For use by the owner of the address_space. - * @i_private_data: For use by the owner of the address_space. */ struct address_space { struct inode *host; @@ -486,7 +485,6 @@ struct address_space { spinlock_t i_private_lock; struct list_head i_private_list; struct rw_semaphore i_mmap_rwsem; - void * i_private_data; } __attribute__((aligned(sizeof(long)))) __randomize_layout; /* * On most architectures that alignment is already the case; but -- cgit v1.2.3 From 521bea7cec8a79684402d555caab408ed43171d5 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 26 Mar 2026 10:54:24 +0100 Subject: fs: Move metadata bhs tracking to a separate struct Instead of tracking metadata bhs for a mapping using i_private_list and i_private_lock create a dedicated mapping_metadata_bhs struct for it. So far this struct is embedded in address_space but that will be switched for per-fs private inode parts later in the series. This also changes the locking from bdev mapping's i_private_lock to a new lock embedded in mapping_metadata_bhs to untangle the i_private_lock locking for maintaining lists of metadata bhs and the locking for looking up / reclaiming bdev's buffer heads. The locking in remove_assoc_map() gets more complex due to this but overall this looks like a reasonable tradeoff. Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260326095354.16340-72-jack@suse.cz Signed-off-by: Christian Brauner --- fs/buffer.c | 138 +++++++++++++++++++++++++---------------------------- fs/inode.c | 2 + include/linux/fs.h | 7 +++ 3 files changed, 74 insertions(+), 73 deletions(-) (limited to 'include') diff --git a/fs/buffer.c b/fs/buffer.c index fa3d84084adf..294f9cd07f42 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -469,30 +469,13 @@ EXPORT_SYMBOL(mark_buffer_async_write); * * The functions mark_buffer_dirty_inode(), fsync_inode_buffers(), * inode_has_buffers() and invalidate_inode_buffers() are provided for the - * management of a list of dependent buffers at ->i_mapping->i_private_list. - * - * Locking is a little subtle: try_to_free_buffers() will remove buffers - * from their controlling inode's queue when they are being freed. But - * try_to_free_buffers() will be operating against the *blockdev* mapping - * at the time, not against the S_ISREG file which depends on those buffers. - * So the locking for i_private_list is via the i_private_lock in the address_space - * which backs the buffers. Which is different from the address_space - * against which the buffers are listed. So for a particular address_space, - * mapping->i_private_lock does *not* protect mapping->i_private_list! In fact, - * mapping->i_private_list will always be protected by the backing blockdev's - * ->i_private_lock. - * - * Which introduces a requirement: all buffers on an address_space's - * ->i_private_list must be from the same address_space: the blockdev's. - * - * address_spaces which do not place buffers at ->i_private_list via these - * utility functions are free to use i_private_lock and i_private_list for - * whatever they want. The only requirement is that list_empty(i_private_list) - * be true at clear_inode() time. - * - * FIXME: clear_inode should not call invalidate_inode_buffers(). The - * filesystems should do that. invalidate_inode_buffers() should just go - * BUG_ON(!list_empty). + * management of a list of dependent buffers in mapping_metadata_bhs struct. + * + * The locking is a little subtle: The list of buffer heads is protected by + * the lock in mapping_metadata_bhs so functions coming from bdev mapping + * (such as try_to_free_buffers()) need to safely get to mapping_metadata_bhs + * using RCU, grab the lock, verify we didn't race with somebody detaching the + * bh / moving it to different inode and only then proceeding. * * FIXME: mark_buffer_dirty_inode() is a data-plane operation. It should * take an address_space, not an inode. And it should be called @@ -509,19 +492,45 @@ EXPORT_SYMBOL(mark_buffer_async_write); * b_inode back. */ -/* - * The buffer's backing address_space's i_private_lock must be held - */ -static void __remove_assoc_queue(struct buffer_head *bh) +static void __remove_assoc_queue(struct mapping_metadata_bhs *mmb, + struct buffer_head *bh) { + lockdep_assert_held(&mmb->lock); list_del_init(&bh->b_assoc_buffers); WARN_ON(!bh->b_assoc_map); bh->b_assoc_map = NULL; } +static void remove_assoc_queue(struct buffer_head *bh) +{ + struct address_space *mapping; + struct mapping_metadata_bhs *mmb; + + /* + * The locking dance is ugly here. We need to acquire the lock + * protecting the metadata bh list while possibly racing with bh + * being removed from the list or moved to a different one. We + * use RCU to pin mapping_metadata_bhs in memory to + * opportunistically acquire the lock and then recheck the bh + * didn't move under us. + */ + while (bh->b_assoc_map) { + rcu_read_lock(); + mapping = READ_ONCE(bh->b_assoc_map); + if (mapping) { + mmb = &mapping->i_metadata_bhs; + spin_lock(&mmb->lock); + if (bh->b_assoc_map == mapping) + __remove_assoc_queue(mmb, bh); + spin_unlock(&mmb->lock); + } + rcu_read_unlock(); + } +} + int inode_has_buffers(struct inode *inode) { - return !list_empty(&inode->i_data.i_private_list); + return !list_empty(&inode->i_data.i_metadata_bhs.list); } EXPORT_SYMBOL_GPL(inode_has_buffers); @@ -529,7 +538,7 @@ EXPORT_SYMBOL_GPL(inode_has_buffers); * sync_mapping_buffers - write out & wait upon a mapping's "associated" buffers * @mapping: the mapping which wants those buffers written * - * Starts I/O against the buffers at mapping->i_private_list, and waits upon + * Starts I/O against the buffers at mapping->i_metadata_bhs and waits upon * that I/O. Basically, this is a convenience function for fsync(). @mapping * is a file or directory which needs those buffers to be written for a * successful fsync(). @@ -548,23 +557,22 @@ EXPORT_SYMBOL_GPL(inode_has_buffers); */ int sync_mapping_buffers(struct address_space *mapping) { - struct address_space *buffer_mapping = - mapping->host->i_sb->s_bdev->bd_mapping; + struct mapping_metadata_bhs *mmb = &mapping->i_metadata_bhs; struct buffer_head *bh; int err = 0; struct blk_plug plug; LIST_HEAD(tmp); - if (list_empty(&mapping->i_private_list)) + if (list_empty(&mmb->list)) return 0; blk_start_plug(&plug); - spin_lock(&buffer_mapping->i_private_lock); - while (!list_empty(&mapping->i_private_list)) { - bh = BH_ENTRY(mapping->i_private_list.next); + spin_lock(&mmb->lock); + while (!list_empty(&mmb->list)) { + bh = BH_ENTRY(mmb->list.next); WARN_ON_ONCE(bh->b_assoc_map != mapping); - __remove_assoc_queue(bh); + __remove_assoc_queue(mmb, bh); /* Avoid race with mark_buffer_dirty_inode() which does * a lockless check and we rely on seeing the dirty bit */ smp_mb(); @@ -573,7 +581,7 @@ int sync_mapping_buffers(struct address_space *mapping) bh->b_assoc_map = mapping; if (buffer_dirty(bh)) { get_bh(bh); - spin_unlock(&buffer_mapping->i_private_lock); + spin_unlock(&mmb->lock); /* * Ensure any pending I/O completes so that * write_dirty_buffer() actually writes the @@ -590,35 +598,34 @@ int sync_mapping_buffers(struct address_space *mapping) * through sync_buffer(). */ brelse(bh); - spin_lock(&buffer_mapping->i_private_lock); + spin_lock(&mmb->lock); } } } - spin_unlock(&buffer_mapping->i_private_lock); + spin_unlock(&mmb->lock); blk_finish_plug(&plug); - spin_lock(&buffer_mapping->i_private_lock); + spin_lock(&mmb->lock); while (!list_empty(&tmp)) { bh = BH_ENTRY(tmp.prev); get_bh(bh); - __remove_assoc_queue(bh); + __remove_assoc_queue(mmb, bh); /* Avoid race with mark_buffer_dirty_inode() which does * a lockless check and we rely on seeing the dirty bit */ smp_mb(); if (buffer_dirty(bh)) { - list_add(&bh->b_assoc_buffers, - &mapping->i_private_list); + list_add(&bh->b_assoc_buffers, &mmb->list); bh->b_assoc_map = mapping; } - spin_unlock(&buffer_mapping->i_private_lock); + spin_unlock(&mmb->lock); wait_on_buffer(bh); if (!buffer_uptodate(bh)) err = -EIO; brelse(bh); - spin_lock(&buffer_mapping->i_private_lock); + spin_lock(&mmb->lock); } - spin_unlock(&buffer_mapping->i_private_lock); + spin_unlock(&mmb->lock); return err; } EXPORT_SYMBOL(sync_mapping_buffers); @@ -715,15 +722,14 @@ void write_boundary_block(struct block_device *bdev, void mark_buffer_dirty_inode(struct buffer_head *bh, struct inode *inode) { struct address_space *mapping = inode->i_mapping; - struct address_space *buffer_mapping = bh->b_folio->mapping; mark_buffer_dirty(bh); if (!bh->b_assoc_map) { - spin_lock(&buffer_mapping->i_private_lock); + spin_lock(&mapping->i_metadata_bhs.lock); list_move_tail(&bh->b_assoc_buffers, - &mapping->i_private_list); + &mapping->i_metadata_bhs.list); bh->b_assoc_map = mapping; - spin_unlock(&buffer_mapping->i_private_lock); + spin_unlock(&mapping->i_metadata_bhs.lock); } } EXPORT_SYMBOL(mark_buffer_dirty_inode); @@ -796,22 +802,16 @@ EXPORT_SYMBOL(block_dirty_folio); * Invalidate any and all dirty buffers on a given inode. We are * probably unmounting the fs, but that doesn't mean we have already * done a sync(). Just drop the buffers from the inode list. - * - * NOTE: we take the inode's blockdev's mapping's i_private_lock. Which - * assumes that all the buffers are against the blockdev. */ void invalidate_inode_buffers(struct inode *inode) { if (inode_has_buffers(inode)) { - struct address_space *mapping = &inode->i_data; - struct list_head *list = &mapping->i_private_list; - struct address_space *buffer_mapping = - mapping->host->i_sb->s_bdev->bd_mapping; - - spin_lock(&buffer_mapping->i_private_lock); - while (!list_empty(list)) - __remove_assoc_queue(BH_ENTRY(list->next)); - spin_unlock(&buffer_mapping->i_private_lock); + struct mapping_metadata_bhs *mmb = &inode->i_data.i_metadata_bhs; + + spin_lock(&mmb->lock); + while (!list_empty(&mmb->list)) + __remove_assoc_queue(mmb, BH_ENTRY(mmb->list.next)); + spin_unlock(&mmb->lock); } } EXPORT_SYMBOL(invalidate_inode_buffers); @@ -1155,14 +1155,7 @@ EXPORT_SYMBOL(__brelse); void __bforget(struct buffer_head *bh) { clear_buffer_dirty(bh); - if (bh->b_assoc_map) { - struct address_space *buffer_mapping = bh->b_folio->mapping; - - spin_lock(&buffer_mapping->i_private_lock); - list_del_init(&bh->b_assoc_buffers); - bh->b_assoc_map = NULL; - spin_unlock(&buffer_mapping->i_private_lock); - } + remove_assoc_queue(bh); __brelse(bh); } EXPORT_SYMBOL(__bforget); @@ -2810,8 +2803,7 @@ drop_buffers(struct folio *folio, struct buffer_head **buffers_to_free) do { struct buffer_head *next = bh->b_this_page; - if (bh->b_assoc_map) - __remove_assoc_queue(bh); + remove_assoc_queue(bh); bh = next; } while (bh != head); *buffers_to_free = head; diff --git a/fs/inode.c b/fs/inode.c index d5774e627a9c..393f586d050a 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -483,6 +483,8 @@ static void __address_space_init_once(struct address_space *mapping) init_rwsem(&mapping->i_mmap_rwsem); INIT_LIST_HEAD(&mapping->i_private_list); spin_lock_init(&mapping->i_private_lock); + spin_lock_init(&mapping->i_metadata_bhs.lock); + INIT_LIST_HEAD(&mapping->i_metadata_bhs.list); mapping->i_mmap = RB_ROOT_CACHED; } diff --git a/include/linux/fs.h b/include/linux/fs.h index d488459396f4..76360b0040e0 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -445,6 +445,12 @@ struct address_space_operations { extern const struct address_space_operations empty_aops; +/* Structure for tracking metadata buffer heads associated with the mapping */ +struct mapping_metadata_bhs { + spinlock_t lock; /* Lock protecting bh list */ + struct list_head list; /* The list of bhs (b_assoc_buffers) */ +}; + /** * struct address_space - Contents of a cacheable, mappable object. * @host: Owner, either the inode or the block_device. @@ -484,6 +490,7 @@ struct address_space { errseq_t wb_err; spinlock_t i_private_lock; struct list_head i_private_list; + struct mapping_metadata_bhs i_metadata_bhs; struct rw_semaphore i_mmap_rwsem; } __attribute__((aligned(sizeof(long)))) __randomize_layout; /* -- cgit v1.2.3 From c86f5d25514c2a60fcf5ea0aa11c5d8bd1a313ef Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 26 Mar 2026 10:54:25 +0100 Subject: fs: Make bhs point to mapping_metadata_bhs Make buffer heads point to mapping_metadata_bhs instead of struct address_space. This makes the code more self contained. For the (only) case of IO error handling where we really need to reach struct address_space add a pointer to the mapping from mapping_metadata_bhs. Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260326095354.16340-73-jack@suse.cz Signed-off-by: Christian Brauner --- fs/buffer.c | 34 ++++++++++++++++------------------ fs/inode.c | 1 + include/linux/buffer_head.h | 4 ++-- include/linux/fs.h | 1 + 4 files changed, 20 insertions(+), 20 deletions(-) (limited to 'include') diff --git a/fs/buffer.c b/fs/buffer.c index 294f9cd07f42..67b3d4624503 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -497,13 +497,12 @@ static void __remove_assoc_queue(struct mapping_metadata_bhs *mmb, { lockdep_assert_held(&mmb->lock); list_del_init(&bh->b_assoc_buffers); - WARN_ON(!bh->b_assoc_map); - bh->b_assoc_map = NULL; + WARN_ON(!bh->b_mmb); + bh->b_mmb = NULL; } static void remove_assoc_queue(struct buffer_head *bh) { - struct address_space *mapping; struct mapping_metadata_bhs *mmb; /* @@ -514,13 +513,12 @@ static void remove_assoc_queue(struct buffer_head *bh) * opportunistically acquire the lock and then recheck the bh * didn't move under us. */ - while (bh->b_assoc_map) { + while (bh->b_mmb) { rcu_read_lock(); - mapping = READ_ONCE(bh->b_assoc_map); - if (mapping) { - mmb = &mapping->i_metadata_bhs; + mmb = READ_ONCE(bh->b_mmb); + if (mmb) { spin_lock(&mmb->lock); - if (bh->b_assoc_map == mapping) + if (bh->b_mmb == mmb) __remove_assoc_queue(mmb, bh); spin_unlock(&mmb->lock); } @@ -551,9 +549,9 @@ EXPORT_SYMBOL_GPL(inode_has_buffers); * Do this in two main stages: first we copy dirty buffers to a * temporary inode list, queueing the writes as we go. Then we clean * up, waiting for those writes to complete. mark_buffer_dirty_inode() - * doesn't touch b_assoc_buffers list if b_assoc_map is not NULL so we - * are sure the buffer stays on our list until IO completes (at which point - * it can be reaped). + * doesn't touch b_assoc_buffers list if b_mmb is not NULL so we are sure the + * buffer stays on our list until IO completes (at which point it can be + * reaped). */ int sync_mapping_buffers(struct address_space *mapping) { @@ -571,14 +569,14 @@ int sync_mapping_buffers(struct address_space *mapping) spin_lock(&mmb->lock); while (!list_empty(&mmb->list)) { bh = BH_ENTRY(mmb->list.next); - WARN_ON_ONCE(bh->b_assoc_map != mapping); + WARN_ON_ONCE(bh->b_mmb != mmb); __remove_assoc_queue(mmb, bh); /* Avoid race with mark_buffer_dirty_inode() which does * a lockless check and we rely on seeing the dirty bit */ smp_mb(); if (buffer_dirty(bh) || buffer_locked(bh)) { list_add(&bh->b_assoc_buffers, &tmp); - bh->b_assoc_map = mapping; + bh->b_mmb = mmb; if (buffer_dirty(bh)) { get_bh(bh); spin_unlock(&mmb->lock); @@ -616,7 +614,7 @@ int sync_mapping_buffers(struct address_space *mapping) smp_mb(); if (buffer_dirty(bh)) { list_add(&bh->b_assoc_buffers, &mmb->list); - bh->b_assoc_map = mapping; + bh->b_mmb = mmb; } spin_unlock(&mmb->lock); wait_on_buffer(bh); @@ -724,11 +722,11 @@ void mark_buffer_dirty_inode(struct buffer_head *bh, struct inode *inode) struct address_space *mapping = inode->i_mapping; mark_buffer_dirty(bh); - if (!bh->b_assoc_map) { + if (!bh->b_mmb) { spin_lock(&mapping->i_metadata_bhs.lock); list_move_tail(&bh->b_assoc_buffers, &mapping->i_metadata_bhs.list); - bh->b_assoc_map = mapping; + bh->b_mmb = &mapping->i_metadata_bhs; spin_unlock(&mapping->i_metadata_bhs.lock); } } @@ -1124,8 +1122,8 @@ void mark_buffer_write_io_error(struct buffer_head *bh) /* FIXME: do we need to set this in both places? */ if (bh->b_folio && bh->b_folio->mapping) mapping_set_error(bh->b_folio->mapping, -EIO); - if (bh->b_assoc_map) - mapping_set_error(bh->b_assoc_map, -EIO); + if (bh->b_mmb) + mapping_set_error(bh->b_mmb->mapping, -EIO); } EXPORT_SYMBOL(mark_buffer_write_io_error); diff --git a/fs/inode.c b/fs/inode.c index 393f586d050a..3874b933abdb 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -276,6 +276,7 @@ int inode_init_always_gfp(struct super_block *sb, struct inode *inode, gfp_t gfp mapping->a_ops = &empty_aops; mapping->host = inode; + mapping->i_metadata_bhs.mapping = mapping; mapping->flags = 0; mapping->wb_err = 0; atomic_set(&mapping->i_mmap_writable, 0); diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index 631bf971efc0..20636599d858 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -73,8 +73,8 @@ struct buffer_head { bh_end_io_t *b_end_io; /* I/O completion */ void *b_private; /* reserved for b_end_io */ struct list_head b_assoc_buffers; /* associated with another mapping */ - struct address_space *b_assoc_map; /* mapping this buffer is - associated with */ + struct mapping_metadata_bhs *b_mmb; /* head of the list of metadata bhs + * this buffer is associated with */ atomic_t b_count; /* users using this buffer_head */ spinlock_t b_uptodate_lock; /* Used by the first bh in a page, to * serialise IO completion of other diff --git a/include/linux/fs.h b/include/linux/fs.h index 76360b0040e0..fa2a812bd718 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -447,6 +447,7 @@ extern const struct address_space_operations empty_aops; /* Structure for tracking metadata buffer heads associated with the mapping */ struct mapping_metadata_bhs { + struct address_space *mapping; /* Mapping bhs are associated with */ spinlock_t lock; /* Lock protecting bh list */ struct list_head list; /* The list of bhs (b_assoc_buffers) */ }; -- cgit v1.2.3 From 025c9af1a20c8353f586c9bfd30705dfe4a277de Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 26 Mar 2026 10:54:26 +0100 Subject: fs: Switch inode_has_buffers() to take mapping_metadata_bhs As part of a move towards placing mapping_metadata_bhs in fs-private inode part, switch inode_has_buffers() to take mapping_metadata_bhs and rename the function to mmb_has_buffers(). Reviewed-by: Christoph Hellwig Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260326095354.16340-74-jack@suse.cz Signed-off-by: Christian Brauner --- fs/buffer.c | 14 +++++++------- fs/ext4/inode.c | 2 +- include/linux/buffer_head.h | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/fs/buffer.c b/fs/buffer.c index 67b3d4624503..b0436481d0f1 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -468,7 +468,7 @@ EXPORT_SYMBOL(mark_buffer_async_write); * written back and waited upon before fsync() returns. * * The functions mark_buffer_dirty_inode(), fsync_inode_buffers(), - * inode_has_buffers() and invalidate_inode_buffers() are provided for the + * mmb_has_buffers() and invalidate_inode_buffers() are provided for the * management of a list of dependent buffers in mapping_metadata_bhs struct. * * The locking is a little subtle: The list of buffer heads is protected by @@ -526,11 +526,11 @@ static void remove_assoc_queue(struct buffer_head *bh) } } -int inode_has_buffers(struct inode *inode) +bool mmb_has_buffers(struct mapping_metadata_bhs *mmb) { - return !list_empty(&inode->i_data.i_metadata_bhs.list); + return !list_empty(&mmb->list); } -EXPORT_SYMBOL_GPL(inode_has_buffers); +EXPORT_SYMBOL_GPL(mmb_has_buffers); /** * sync_mapping_buffers - write out & wait upon a mapping's "associated" buffers @@ -561,7 +561,7 @@ int sync_mapping_buffers(struct address_space *mapping) struct blk_plug plug; LIST_HEAD(tmp); - if (list_empty(&mmb->list)) + if (!mmb_has_buffers(mmb)) return 0; blk_start_plug(&plug); @@ -803,9 +803,9 @@ EXPORT_SYMBOL(block_dirty_folio); */ void invalidate_inode_buffers(struct inode *inode) { - if (inode_has_buffers(inode)) { - struct mapping_metadata_bhs *mmb = &inode->i_data.i_metadata_bhs; + struct mapping_metadata_bhs *mmb = &inode->i_data.i_metadata_bhs; + if (mmb_has_buffers(mmb)) { spin_lock(&mmb->lock); while (!list_empty(&mmb->list)) __remove_assoc_queue(mmb, BH_ENTRY(mmb->list.next)); diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 6f892abef003..011cb2eb16a2 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -3436,7 +3436,7 @@ static bool ext4_inode_datasync_dirty(struct inode *inode) } /* Any metadata buffers to write? */ - if (inode_has_buffers(inode)) + if (mmb_has_buffers(&inode->i_mapping->i_metadata_bhs)) return true; return inode_state_read_once(inode) & I_DIRTY_DATASYNC; } diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index 20636599d858..44094fd476f5 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -515,7 +515,7 @@ bool block_dirty_folio(struct address_space *mapping, struct folio *folio); void buffer_init(void); bool try_to_free_buffers(struct folio *folio); -int inode_has_buffers(struct inode *inode); +bool mmb_has_buffers(struct mapping_metadata_bhs *mmb); void invalidate_inode_buffers(struct inode *inode); int sync_mapping_buffers(struct address_space *mapping); void invalidate_bh_lrus(void); -- cgit v1.2.3 From a8c8122a3dac55d25a1912b8fec9b8cd7366c37a Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 26 Mar 2026 10:54:27 +0100 Subject: fs: Provide functions for handling mapping_metadata_bhs directly As part of transition toward moving mapping_metadata_bhs to fs-private part of the inode, provide functions for operations on this list directly instead of going through the inode / mapping. Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260326095354.16340-75-jack@suse.cz Signed-off-by: Christian Brauner --- fs/buffer.c | 110 ++++++++++++++++++++------------------------ include/linux/buffer_head.h | 44 ++++++++++++++---- 2 files changed, 87 insertions(+), 67 deletions(-) (limited to 'include') diff --git a/fs/buffer.c b/fs/buffer.c index b0436481d0f1..cbed175f418b 100644 --- a/fs/buffer.c +++ b/fs/buffer.c @@ -467,31 +467,25 @@ EXPORT_SYMBOL(mark_buffer_async_write); * a successful fsync(). For example, ext2 indirect blocks need to be * written back and waited upon before fsync() returns. * - * The functions mark_buffer_dirty_inode(), fsync_inode_buffers(), - * mmb_has_buffers() and invalidate_inode_buffers() are provided for the - * management of a list of dependent buffers in mapping_metadata_bhs struct. + * The functions mmb_mark_buffer_dirty(), mmb_sync(), mmb_has_buffers() + * and mmb_invalidate() are provided for the management of a list of dependent + * buffers in mapping_metadata_bhs struct. * * The locking is a little subtle: The list of buffer heads is protected by * the lock in mapping_metadata_bhs so functions coming from bdev mapping * (such as try_to_free_buffers()) need to safely get to mapping_metadata_bhs * using RCU, grab the lock, verify we didn't race with somebody detaching the * bh / moving it to different inode and only then proceeding. - * - * FIXME: mark_buffer_dirty_inode() is a data-plane operation. It should - * take an address_space, not an inode. And it should be called - * mark_buffer_dirty_fsync() to clearly define why those buffers are being - * queued up. - * - * FIXME: mark_buffer_dirty_inode() doesn't need to add the buffer to the - * list if it is already on a list. Because if the buffer is on a list, - * it *must* already be on the right one. If not, the filesystem is being - * silly. This will save a ton of locking. But first we have to ensure - * that buffers are taken *off* the old inode's list when they are freed - * (presumably in truncate). That requires careful auditing of all - * filesystems (do it inside bforget()). It could also be done by bringing - * b_inode back. */ +void mmb_init(struct mapping_metadata_bhs *mmb, struct address_space *mapping) +{ + spin_lock_init(&mmb->lock); + INIT_LIST_HEAD(&mmb->list); + mmb->mapping = mapping; +} +EXPORT_SYMBOL(mmb_init); + static void __remove_assoc_queue(struct mapping_metadata_bhs *mmb, struct buffer_head *bh) { @@ -533,12 +527,12 @@ bool mmb_has_buffers(struct mapping_metadata_bhs *mmb) EXPORT_SYMBOL_GPL(mmb_has_buffers); /** - * sync_mapping_buffers - write out & wait upon a mapping's "associated" buffers - * @mapping: the mapping which wants those buffers written + * mmb_sync - write out & wait upon all buffers in a list + * @mmb: the list of buffers to write * - * Starts I/O against the buffers at mapping->i_metadata_bhs and waits upon - * that I/O. Basically, this is a convenience function for fsync(). @mapping - * is a file or directory which needs those buffers to be written for a + * Starts I/O against the buffers in the given list and waits upon + * that I/O. Basically, this is a convenience function for fsync(). @mmb is + * for a file or directory which needs those buffers to be written for a * successful fsync(). * * We have conflicting pressures: we want to make sure that all @@ -553,9 +547,8 @@ EXPORT_SYMBOL_GPL(mmb_has_buffers); * buffer stays on our list until IO completes (at which point it can be * reaped). */ -int sync_mapping_buffers(struct address_space *mapping) +int mmb_sync(struct mapping_metadata_bhs *mmb) { - struct mapping_metadata_bhs *mmb = &mapping->i_metadata_bhs; struct buffer_head *bh; int err = 0; struct blk_plug plug; @@ -626,33 +619,35 @@ int sync_mapping_buffers(struct address_space *mapping) spin_unlock(&mmb->lock); return err; } -EXPORT_SYMBOL(sync_mapping_buffers); +EXPORT_SYMBOL(mmb_sync); /** - * generic_buffers_fsync_noflush - generic buffer fsync implementation - * for simple filesystems with no inode lock + * mmb_fsync_noflush - fsync implementation for simple filesystems with + * metadata buffers list * * @file: file to synchronize + * @mmb: list of metadata bhs to flush * @start: start offset in bytes * @end: end offset in bytes (inclusive) * @datasync: only synchronize essential metadata if true * - * This is a generic implementation of the fsync method for simple - * filesystems which track all non-inode metadata in the buffers list - * hanging off the address_space structure. + * This is an implementation of the fsync method for simple filesystems which + * track all non-inode metadata in the buffers list hanging off the @mmb + * structure. */ -int generic_buffers_fsync_noflush(struct file *file, loff_t start, loff_t end, - bool datasync) +int mmb_fsync_noflush(struct file *file, struct mapping_metadata_bhs *mmb, + loff_t start, loff_t end, bool datasync) { struct inode *inode = file->f_mapping->host; int err; - int ret; + int ret = 0; err = file_write_and_wait_range(file, start, end); if (err) return err; - ret = sync_mapping_buffers(inode->i_mapping); + if (mmb) + ret = mmb_sync(mmb); if (!(inode_state_read_once(inode) & I_DIRTY_ALL)) goto out; if (datasync && !(inode_state_read_once(inode) & I_DIRTY_DATASYNC)) @@ -669,34 +664,35 @@ out: ret = err; return ret; } -EXPORT_SYMBOL(generic_buffers_fsync_noflush); +EXPORT_SYMBOL(mmb_fsync_noflush); /** - * generic_buffers_fsync - generic buffer fsync implementation - * for simple filesystems with no inode lock + * mmb_fsync - fsync implementation for simple filesystems with metadata + * buffers list * * @file: file to synchronize + * @mmb: list of metadata bhs to flush * @start: start offset in bytes * @end: end offset in bytes (inclusive) * @datasync: only synchronize essential metadata if true * - * This is a generic implementation of the fsync method for simple - * filesystems which track all non-inode metadata in the buffers list - * hanging off the address_space structure. This also makes sure that - * a device cache flush operation is called at the end. + * This is an implementation of the fsync method for simple filesystems which + * track all non-inode metadata in the buffers list hanging off the @mmb + * structure. This also makes sure that a device cache flush operation is + * called at the end. */ -int generic_buffers_fsync(struct file *file, loff_t start, loff_t end, - bool datasync) +int mmb_fsync(struct file *file, struct mapping_metadata_bhs *mmb, + loff_t start, loff_t end, bool datasync) { struct inode *inode = file->f_mapping->host; int ret; - ret = generic_buffers_fsync_noflush(file, start, end, datasync); + ret = mmb_fsync_noflush(file, mmb, start, end, datasync); if (!ret) ret = blkdev_issue_flush(inode->i_sb->s_bdev); return ret; } -EXPORT_SYMBOL(generic_buffers_fsync); +EXPORT_SYMBOL(mmb_fsync); /* * Called when we've recently written block `bblock', and it is known that @@ -717,20 +713,18 @@ void write_boundary_block(struct block_device *bdev, } } -void mark_buffer_dirty_inode(struct buffer_head *bh, struct inode *inode) +void mmb_mark_buffer_dirty(struct buffer_head *bh, + struct mapping_metadata_bhs *mmb) { - struct address_space *mapping = inode->i_mapping; - mark_buffer_dirty(bh); if (!bh->b_mmb) { - spin_lock(&mapping->i_metadata_bhs.lock); - list_move_tail(&bh->b_assoc_buffers, - &mapping->i_metadata_bhs.list); - bh->b_mmb = &mapping->i_metadata_bhs; - spin_unlock(&mapping->i_metadata_bhs.lock); + spin_lock(&mmb->lock); + list_move_tail(&bh->b_assoc_buffers, &mmb->list); + bh->b_mmb = mmb; + spin_unlock(&mmb->lock); } } -EXPORT_SYMBOL(mark_buffer_dirty_inode); +EXPORT_SYMBOL(mmb_mark_buffer_dirty); /** * block_dirty_folio - Mark a folio as dirty. @@ -797,14 +791,12 @@ bool block_dirty_folio(struct address_space *mapping, struct folio *folio) EXPORT_SYMBOL(block_dirty_folio); /* - * Invalidate any and all dirty buffers on a given inode. We are + * Invalidate any and all dirty buffers on a given buffers list. We are * probably unmounting the fs, but that doesn't mean we have already * done a sync(). Just drop the buffers from the inode list. */ -void invalidate_inode_buffers(struct inode *inode) +void mmb_invalidate(struct mapping_metadata_bhs *mmb) { - struct mapping_metadata_bhs *mmb = &inode->i_data.i_metadata_bhs; - if (mmb_has_buffers(mmb)) { spin_lock(&mmb->lock); while (!list_empty(&mmb->list)) @@ -812,7 +804,7 @@ void invalidate_inode_buffers(struct inode *inode) spin_unlock(&mmb->lock); } } -EXPORT_SYMBOL(invalidate_inode_buffers); +EXPORT_SYMBOL(mmb_invalidate); /* * Create the appropriate buffers when given a folio for data area and diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index 44094fd476f5..e207dcca7a25 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -205,12 +205,30 @@ struct buffer_head *create_empty_buffers(struct folio *folio, void end_buffer_read_sync(struct buffer_head *bh, int uptodate); void end_buffer_write_sync(struct buffer_head *bh, int uptodate); -/* Things to do with buffers at mapping->private_list */ -void mark_buffer_dirty_inode(struct buffer_head *bh, struct inode *inode); -int generic_buffers_fsync_noflush(struct file *file, loff_t start, loff_t end, - bool datasync); -int generic_buffers_fsync(struct file *file, loff_t start, loff_t end, - bool datasync); +/* Things to do with metadata buffers list */ +void mmb_mark_buffer_dirty(struct buffer_head *bh, struct mapping_metadata_bhs *mmb); +static inline void mark_buffer_dirty_inode(struct buffer_head *bh, + struct inode *inode) +{ + mmb_mark_buffer_dirty(bh, &inode->i_data.i_metadata_bhs); +} +int mmb_fsync_noflush(struct file *file, struct mapping_metadata_bhs *mmb, + loff_t start, loff_t end, bool datasync); +static inline int generic_buffers_fsync_noflush(struct file *file, + loff_t start, loff_t end, + bool datasync) +{ + return mmb_fsync_noflush(file, &file->f_mapping->i_metadata_bhs, + start, end, datasync); +} +int mmb_fsync(struct file *file, struct mapping_metadata_bhs *mmb, + loff_t start, loff_t end, bool datasync); +static inline int generic_buffers_fsync(struct file *file, + loff_t start, loff_t end, bool datasync) +{ + return mmb_fsync(file, &file->f_mapping->i_metadata_bhs, + start, end, datasync); +} void clean_bdev_aliases(struct block_device *bdev, sector_t block, sector_t len); static inline void clean_bdev_bh_alias(struct buffer_head *bh) @@ -515,9 +533,18 @@ bool block_dirty_folio(struct address_space *mapping, struct folio *folio); void buffer_init(void); bool try_to_free_buffers(struct folio *folio); +void mmb_init(struct mapping_metadata_bhs *mmb, struct address_space *mapping); bool mmb_has_buffers(struct mapping_metadata_bhs *mmb); -void invalidate_inode_buffers(struct inode *inode); -int sync_mapping_buffers(struct address_space *mapping); +void mmb_invalidate(struct mapping_metadata_bhs *mmb); +int mmb_sync(struct mapping_metadata_bhs *mmb); +static inline void invalidate_inode_buffers(struct inode *inode) +{ + mmb_invalidate(&inode->i_data.i_metadata_bhs); +} +static inline int sync_mapping_buffers(struct address_space *mapping) +{ + return mmb_sync(&mapping->i_metadata_bhs); +} void invalidate_bh_lrus(void); void invalidate_bh_lrus_cpu(void); bool has_bh_in_lru(int cpu, void *dummy); @@ -527,6 +554,7 @@ extern int buffer_heads_over_limit; static inline void buffer_init(void) {} static inline bool try_to_free_buffers(struct folio *folio) { return true; } +static inline int mmb_sync(struct mapping_metadata_bhs *mmb) { return 0; } static inline void invalidate_inode_buffers(struct inode *inode) {} static inline int sync_mapping_buffers(struct address_space *mapping) { return 0; } static inline void invalidate_bh_lrus(void) {} -- cgit v1.2.3 From cb6d109b9ccc374d09812c2387ab826499ee6562 Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 26 Mar 2026 10:54:35 +0100 Subject: fs: Drop mapping_metadata_bhs from address space Nobody uses mapping_metadata_bhs in struct address_space anymore. Just remove it and with it all helper functions using it. Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260326095354.16340-83-jack@suse.cz Signed-off-by: Christian Brauner --- fs/inode.c | 3 --- include/linux/buffer_head.h | 28 ---------------------------- include/linux/fs.h | 1 - 3 files changed, 32 deletions(-) (limited to 'include') diff --git a/fs/inode.c b/fs/inode.c index 3874b933abdb..d5774e627a9c 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -276,7 +276,6 @@ int inode_init_always_gfp(struct super_block *sb, struct inode *inode, gfp_t gfp mapping->a_ops = &empty_aops; mapping->host = inode; - mapping->i_metadata_bhs.mapping = mapping; mapping->flags = 0; mapping->wb_err = 0; atomic_set(&mapping->i_mmap_writable, 0); @@ -484,8 +483,6 @@ static void __address_space_init_once(struct address_space *mapping) init_rwsem(&mapping->i_mmap_rwsem); INIT_LIST_HEAD(&mapping->i_private_list); spin_lock_init(&mapping->i_private_lock); - spin_lock_init(&mapping->i_metadata_bhs.lock); - INIT_LIST_HEAD(&mapping->i_metadata_bhs.list); mapping->i_mmap = RB_ROOT_CACHED; } diff --git a/include/linux/buffer_head.h b/include/linux/buffer_head.h index e207dcca7a25..e4939e33b4b5 100644 --- a/include/linux/buffer_head.h +++ b/include/linux/buffer_head.h @@ -207,28 +207,10 @@ void end_buffer_write_sync(struct buffer_head *bh, int uptodate); /* Things to do with metadata buffers list */ void mmb_mark_buffer_dirty(struct buffer_head *bh, struct mapping_metadata_bhs *mmb); -static inline void mark_buffer_dirty_inode(struct buffer_head *bh, - struct inode *inode) -{ - mmb_mark_buffer_dirty(bh, &inode->i_data.i_metadata_bhs); -} int mmb_fsync_noflush(struct file *file, struct mapping_metadata_bhs *mmb, loff_t start, loff_t end, bool datasync); -static inline int generic_buffers_fsync_noflush(struct file *file, - loff_t start, loff_t end, - bool datasync) -{ - return mmb_fsync_noflush(file, &file->f_mapping->i_metadata_bhs, - start, end, datasync); -} int mmb_fsync(struct file *file, struct mapping_metadata_bhs *mmb, loff_t start, loff_t end, bool datasync); -static inline int generic_buffers_fsync(struct file *file, - loff_t start, loff_t end, bool datasync) -{ - return mmb_fsync(file, &file->f_mapping->i_metadata_bhs, - start, end, datasync); -} void clean_bdev_aliases(struct block_device *bdev, sector_t block, sector_t len); static inline void clean_bdev_bh_alias(struct buffer_head *bh) @@ -537,14 +519,6 @@ void mmb_init(struct mapping_metadata_bhs *mmb, struct address_space *mapping); bool mmb_has_buffers(struct mapping_metadata_bhs *mmb); void mmb_invalidate(struct mapping_metadata_bhs *mmb); int mmb_sync(struct mapping_metadata_bhs *mmb); -static inline void invalidate_inode_buffers(struct inode *inode) -{ - mmb_invalidate(&inode->i_data.i_metadata_bhs); -} -static inline int sync_mapping_buffers(struct address_space *mapping) -{ - return mmb_sync(&mapping->i_metadata_bhs); -} void invalidate_bh_lrus(void); void invalidate_bh_lrus_cpu(void); bool has_bh_in_lru(int cpu, void *dummy); @@ -555,8 +529,6 @@ extern int buffer_heads_over_limit; static inline void buffer_init(void) {} static inline bool try_to_free_buffers(struct folio *folio) { return true; } static inline int mmb_sync(struct mapping_metadata_bhs *mmb) { return 0; } -static inline void invalidate_inode_buffers(struct inode *inode) {} -static inline int sync_mapping_buffers(struct address_space *mapping) { return 0; } static inline void invalidate_bh_lrus(void) {} static inline void invalidate_bh_lrus_cpu(void) {} static inline bool has_bh_in_lru(int cpu, void *dummy) { return false; } diff --git a/include/linux/fs.h b/include/linux/fs.h index fa2a812bd718..ccfa696253c8 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -491,7 +491,6 @@ struct address_space { errseq_t wb_err; spinlock_t i_private_lock; struct list_head i_private_list; - struct mapping_metadata_bhs i_metadata_bhs; struct rw_semaphore i_mmap_rwsem; } __attribute__((aligned(sizeof(long)))) __randomize_layout; /* -- cgit v1.2.3 From f219798ce294e346031022a85670f68eb2dec10e Mon Sep 17 00:00:00 2001 From: Jan Kara Date: Thu, 26 Mar 2026 10:54:36 +0100 Subject: fs: Drop i_private_list from address_space Nobody is using i_private_list anymore. Remove it. Signed-off-by: Jan Kara Link: https://patch.msgid.link/20260326095354.16340-84-jack@suse.cz Tested-by: syzbot@syzkaller.appspotmail.com Signed-off-by: Christian Brauner --- fs/inode.c | 2 -- include/linux/fs.h | 2 -- 2 files changed, 4 deletions(-) (limited to 'include') diff --git a/fs/inode.c b/fs/inode.c index d5774e627a9c..a8f019078fab 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -481,7 +481,6 @@ static void __address_space_init_once(struct address_space *mapping) { xa_init_flags(&mapping->i_pages, XA_FLAGS_LOCK_IRQ | XA_FLAGS_ACCOUNT); init_rwsem(&mapping->i_mmap_rwsem); - INIT_LIST_HEAD(&mapping->i_private_list); spin_lock_init(&mapping->i_private_lock); mapping->i_mmap = RB_ROOT_CACHED; } @@ -795,7 +794,6 @@ void clear_inode(struct inode *inode) * nor even WARN_ON(!mapping_empty). */ xa_unlock_irq(&inode->i_data.i_pages); - BUG_ON(!list_empty(&inode->i_data.i_private_list)); BUG_ON(!(inode_state_read_once(inode) & I_FREEING)); BUG_ON(inode_state_read_once(inode) & I_CLEAR); BUG_ON(!list_empty(&inode->i_wb_list)); diff --git a/include/linux/fs.h b/include/linux/fs.h index ccfa696253c8..a3bed26d066d 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -471,7 +471,6 @@ struct mapping_metadata_bhs { * @flags: Error bits and flags (AS_*). * @wb_err: The most recent error which has occurred. * @i_private_lock: For use by the owner of the address_space. - * @i_private_list: For use by the owner of the address_space. */ struct address_space { struct inode *host; @@ -490,7 +489,6 @@ struct address_space { unsigned long flags; errseq_t wb_err; spinlock_t i_private_lock; - struct list_head i_private_list; struct rw_semaphore i_mmap_rwsem; } __attribute__((aligned(sizeof(long)))) __randomize_layout; /* -- cgit v1.2.3