From patchwork Fri Jan 2 00:50:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1179 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767315138; bh=x5k+rBIil0u2ygwLb11mvDNXae1RJgvv7TuJSb7eqRw=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=LvTYeU27HdM4uPAmUm5ZdUPEh2rXWVBsXz2zNrQMQGQzG0Slnqcfu4pbNDifquuzj gOdBikhdC8kjdb519GFlR0P6wi0/1Fs8zzB3TKuWxEKyX00uPTf5HxMJ4x1C+5FcCv Dnj4d+nIpO2YEuU381dzzDHncLY6KoWRrVX1pGXd+cIqneAeaXdNQUHAeUfzkk8zZC 1EaqXtwf7idAwCbUmRVKxD3/+WLiFe8Tw/PUzSJIqG5SINm14iD9xln7eFyK//aBA4 Pt9u90TSkIelZ7De3iSs8iVrJh5t3UptWT0+Ybl1p1PL1DdIGUX+IATlrApnBstf/s /VZovLpLBsWDg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D508C69007 for ; Thu, 1 Jan 2026 17:52:18 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id iuIKDcJYRo8x for ; Thu, 1 Jan 2026 17:52:18 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767315138; bh=x5k+rBIil0u2ygwLb11mvDNXae1RJgvv7TuJSb7eqRw=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=LvTYeU27HdM4uPAmUm5ZdUPEh2rXWVBsXz2zNrQMQGQzG0Slnqcfu4pbNDifquuzj gOdBikhdC8kjdb519GFlR0P6wi0/1Fs8zzB3TKuWxEKyX00uPTf5HxMJ4x1C+5FcCv Dnj4d+nIpO2YEuU381dzzDHncLY6KoWRrVX1pGXd+cIqneAeaXdNQUHAeUfzkk8zZC 1EaqXtwf7idAwCbUmRVKxD3/+WLiFe8Tw/PUzSJIqG5SINm14iD9xln7eFyK//aBA4 Pt9u90TSkIelZ7De3iSs8iVrJh5t3UptWT0+Ybl1p1PL1DdIGUX+IATlrApnBstf/s /VZovLpLBsWDg== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id BD85268FFD for ; Thu, 1 Jan 2026 17:52:18 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767315136; bh=DEpoGdp2/z251984gp2334Q4QRz3gTg6gYAF3/CtIzY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qGgX95upDtXERa+yqSr91Ylm81PmQM1+UZJrt3zuEKoV1pYpdS+/qd7qBj41rCbAf 6kuzskllbAslZ1DhG2jHqGpZkqZBu4rtxgPo5ExrRZXNQLKjq/iK5D5IPVllLXtWNW hsvgQ2lGo2QNd2LskUhIZ5COrQ9Q5KCU9RLSLWh7kboIe/5G0bVMhd5nzjPgiXO787 WG2cS848ywABSCuTjow7BGHis4BX2COHUoELSzUIPsbnQGH8T71jUQhN9H/jS1TmrI uoRcOmWwthQzPxb5PwC3t9yMJ5YjgYuGZfGmyZf5HPFE25omYD+64NSKAs7+QtkLGM N1GKBV61F0+rg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id C968868FFD; Thu, 1 Jan 2026 17:52:16 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id SPVmRhtIT9yK; Thu, 1 Jan 2026 17:52:16 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767315131; bh=rYMUK32LL51aSKz35OGM6a/fUrg4jcMYPiQTOpso2zc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ISHRpREsH1TUtwlN418kKou/ESSKczPL6bmBwA/w8oSjvpCwGtU/kma4sgpIjRrjQ rIzgKk8hPVPgCXmku+Hi2YPeA1aBzIzLYcZ7yrCFGpcJp5DXFgP/SUZTiu32JaQJKd BEw/8H+XI1M+asIAUd2G83K1eGVZ+vdPzg8GTZYhaezHjPt7eBj08F6gcPdgfusrOw I+cFGg1GUFtwwYBnz/JMOZ2cGMOfExd68/wWamsxjC5P0xdGCUrUvc65OCPiQcYDON RZehrGSeiqpdOEuiuIJzo2ExBe9WTRv03d5kxGx41JmRS6WJ0TOJV82KFz/szcXCoW IShvEpxywVTzw== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id EEF8E69004; Thu, 1 Jan 2026 17:52:10 -0700 (MST) From: Simon Glass To: U-Boot Concept Date: Thu, 1 Jan 2026 17:50:45 -0700 Message-ID: <20260102005112.552256-16-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260102005112.552256-1-sjg@u-boot.org> References: <20260102005112.552256-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: EGFP3B7EUJAR2UCDFSVTLOVBFXECDYBU X-Message-ID-Hash: EGFP3B7EUJAR2UCDFSVTLOVBFXECDYBU X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Heinrich Schuchardt , Simon Glass , Claude X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH v2 15/30] ext4l: Clean up fully when unmounting List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Resources are not properly released on unmount, causing memory leaks in long-running U-Boot sessions that remount filesystems. Add ext4l_free_sb() to release all resources on unmount: - Destroy journal and commit superblock - Release superblock buffer and unregister lazy init - Free mballoc data and release system zone - Destroy xattr caches - Free group descriptors and flex groups - Evict all tracked inodes - Free root dentry, sbi, and superblock structures Also: - Init the s_inodes list when allocating superblock - Free mount context (ctx, fc) after successful mount - Call destroy_inodecache() during global cleanup - Clear folio cache on buddy cache inode before iput() Co-developed-by: Claude Signed-off-by: Simon Glass --- (no changes since v1) fs/ext4l/ext4.h | 6 ++ fs/ext4l/ext4_uboot.h | 2 + fs/ext4l/interface.c | 135 ++++++++++++++++++++++++++++++++++++++++++ fs/ext4l/mballoc.c | 4 ++ fs/ext4l/super.c | 25 ++++++-- fs/ext4l/support.c | 24 ++++++++ 6 files changed, 191 insertions(+), 5 deletions(-) diff --git a/fs/ext4l/ext4.h b/fs/ext4l/ext4.h index 669d5522f27..a1c80dd7cdf 100644 --- a/fs/ext4l/ext4.h +++ b/fs/ext4l/ext4.h @@ -3947,4 +3947,10 @@ extern int ext4_block_write_begin(handle_t *handle, struct folio *folio, #define EFSBADCRC EBADMSG /* Bad CRC detected */ #define EFSCORRUPTED EUCLEAN /* Filesystem is corrupted */ +/* Cleanup functions exported from super.c */ +void ext4_group_desc_free(struct ext4_sb_info *sbi); +void ext4_flex_groups_free(struct ext4_sb_info *sbi); +void ext4_destroy_lazy_init(void); +void destroy_inodecache(void); + #endif /* _EXT4_H */ diff --git a/fs/ext4l/ext4_uboot.h b/fs/ext4l/ext4_uboot.h index 588670a8b62..70ac403d9fd 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -1395,6 +1395,7 @@ struct folio *__filemap_get_folio(struct address_space *mapping, gfp_t gfp); void folio_put(struct folio *folio); void folio_get(struct folio *folio); +void mapping_clear_folio_cache(struct address_space *mapping); /* projid_t - project ID type */ typedef unsigned int projid_t; @@ -2095,6 +2096,7 @@ struct fs_context { /* ext4 superblock initialisation and commit */ int ext4_fill_super(struct super_block *sb, struct fs_context *fc); int ext4_commit_super(struct super_block *sb); +void ext4_unregister_li_request(struct super_block *sb); /* fs_parameter stubs */ struct fs_parameter { diff --git a/fs/ext4l/interface.c b/fs/ext4l/interface.c index aebcc17fd3a..ceedabdb727 100644 --- a/fs/ext4l/interface.c +++ b/fs/ext4l/interface.c @@ -22,6 +22,8 @@ #include "ext4_uboot.h" #include "ext4.h" +#include "ext4_jbd2.h" +#include "xattr.h" /* Global state */ static struct blk_desc *ext4l_dev_desc; @@ -142,6 +144,134 @@ void ext4l_clear_blk_dev(void) ext4l_mounted = 0; } +/** + * ext4l_free_sb() - Free superblock and associated resources + * @sb: Superblock to free + * @skip_io: If true, skip all I/O operations (for forced close) + * + * Releases all resources associated with the superblock including the journal, + * caches, inodes, and the superblock structure itself. + */ +static void ext4l_free_sb(struct super_block *sb, bool skip_io) +{ + struct ext4_sb_info *sbi = EXT4_SB(sb); + + /* + * Destroy journal first to properly clean up all buffers. + * If skip_io is set, the device may be invalid so skip + * journal destroy entirely - it will be recovered on next mount. + */ + if (sbi->s_journal && !skip_io) + ext4_journal_destroy(sbi, sbi->s_journal); + + /* Commit superblock if device is valid and I/O is allowed */ + if (!skip_io) + ext4_commit_super(sb); + + /* Release superblock buffer */ + brelse(sbi->s_sbh); + + /* Unregister lazy init and free if no longer needed */ + ext4_unregister_li_request(sb); + ext4_destroy_lazy_init(); + + /* Free mballoc data */ + ext4_mb_release(sb); + + /* Release system zone */ + ext4_release_system_zone(sb); + + /* Destroy xattr caches */ + ext4_xattr_destroy_cache(sbi->s_ea_inode_cache); + sbi->s_ea_inode_cache = NULL; + ext4_xattr_destroy_cache(sbi->s_ea_block_cache); + sbi->s_ea_block_cache = NULL; + + /* Free group descriptors and flex groups */ + ext4_group_desc_free(sbi); + ext4_flex_groups_free(sbi); + + /* Evict all inodes before destroying caches */ + while (!list_empty(&sb->s_inodes)) { + struct inode *inode; + struct ext4_inode_info *ei; + + inode = list_first_entry(&sb->s_inodes, + struct inode, i_sb_list); + list_del_init(&inode->i_sb_list); + /* Clear extent status and free the inode */ + ext4_es_remove_extent(inode, 0, EXT_MAX_BLOCKS); + ei = EXT4_I(inode); + kfree(ei); + } + + /* Free root dentry */ + if (sb->s_root) { + kfree(sb->s_root); + sb->s_root = NULL; + } + + /* Free sbi */ + kfree(sbi->s_blockgroup_lock); + kfree(sbi); + + /* Free structures allocated in ext4l_probe() */ + kfree(sb->s_bdev->bd_mapping); + kfree(sb->s_bdev); + kfree(sb); +} + +/** + * ext4l_close_internal() - Internal close function + * @skip_io: If true, skip all I/O operations (for forced close) + * + * When called from the safeguard in ext4l_probe(), the device may be + * invalid (rebound to a different file), so skip_io should be true to + * avoid crashes when trying to write to the device. + */ +static void ext4l_close_internal(bool skip_io) +{ + struct super_block *sb = ext4l_sb; + + if (ext4l_open_dirs > 0) + return; + + if (sb) + ext4l_free_sb(sb, skip_io); + + ext4l_dev_desc = NULL; + ext4l_sb = NULL; + + /* + * Force cleanup of any remaining journal_heads before clearing + * the buffer cache. This ensures no stale journal_head references + * survive to the next mount. This is critical even when skip_io + * is true - we MUST disconnect journal_heads before freeing + * buffer_heads to avoid dangling pointers. + */ + bh_cache_release_jbd(); + + ext4l_clear_blk_dev(); + + /* + * Clean up ext4 and JBD2 global state so it can be properly + * reinitialised on the next mount. This is important in U-Boot + * where we may mount/unmount filesystems multiple times in a + * single session. + * + * Even when skip_io is true (journal wasn't properly destroyed), + * we must destroy the caches to free all orphaned journal_heads. + * The next mount will reinitialise fresh caches. + */ + ext4_exit_system_zone(); + ext4_exit_es(); + if (IS_ENABLED(CONFIG_EXT4_WRITE)) + ext4_exit_mballoc(); + if (IS_ENABLED(CONFIG_EXT4_JOURNAL)) + jbd2_journal_exit_global(); + destroy_inodecache(); +} + int ext4l_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition) { @@ -192,6 +322,7 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, ret = -ENOMEM; goto err_exit_es; } + INIT_LIST_HEAD(&sb->s_inodes); /* Allocate block_device */ sb->s_bdev = kzalloc(sizeof(struct block_device), GFP_KERNEL); @@ -279,6 +410,10 @@ int ext4l_probe(struct blk_desc *fs_dev_desc, /* Store super_block for later operations */ ext4l_sb = sb; + /* Free mount context - no longer needed after successful mount */ + kfree(ctx); + kfree(fc); + /* Print messages if ext4l_msgs environment variable is set */ if (env_get_yesno("ext4l_msgs") == 1) ext4l_print_msgs(); diff --git a/fs/ext4l/mballoc.c b/fs/ext4l/mballoc.c index 47863efd1cb..1643fd54f27 100644 --- a/fs/ext4l/mballoc.c +++ b/fs/ext4l/mballoc.c @@ -3890,6 +3890,10 @@ void ext4_mb_release(struct super_block *sb) ext4_mb_largest_free_orders_destroy(sbi); kfree(sbi->s_mb_offsets); kfree(sbi->s_mb_maxs); +#ifdef __UBOOT__ + if (sbi->s_buddy_cache) + mapping_clear_folio_cache(sbi->s_buddy_cache->i_mapping); +#endif iput(sbi->s_buddy_cache); if (sbi->s_mb_stats) { ext4_msg(sb, KERN_INFO, diff --git a/fs/ext4l/super.c b/fs/ext4l/super.c index 9ed6f907b7a..48c87eb0e97 100644 --- a/fs/ext4l/super.c +++ b/fs/ext4l/super.c @@ -48,7 +48,6 @@ static int ext4_unfreeze(struct super_block *sb); static int ext4_freeze(struct super_block *sb); static inline int ext2_feature_set_ok(struct super_block *sb); static inline int ext3_feature_set_ok(struct super_block *sb); -static void ext4_unregister_li_request(struct super_block *sb); static void ext4_clear_request_list(void); static struct inode *ext4_get_journal_inode(struct super_block *sb, unsigned int journal_inum); @@ -1228,7 +1227,7 @@ static void ext4_percpu_param_destroy(struct ext4_sb_info *sbi) percpu_free_rwsem(&sbi->s_writepages_rwsem); } -static void ext4_group_desc_free(struct ext4_sb_info *sbi) +void ext4_group_desc_free(struct ext4_sb_info *sbi) { struct buffer_head **group_desc; int i; @@ -1241,7 +1240,7 @@ static void ext4_group_desc_free(struct ext4_sb_info *sbi) rcu_read_unlock(); } -static void ext4_flex_groups_free(struct ext4_sb_info *sbi) +void ext4_flex_groups_free(struct ext4_sb_info *sbi) { struct flex_groups **flex_groups; int i; @@ -1484,7 +1483,7 @@ static int __init init_inodecache(void) return 0; } -static void destroy_inodecache(void) +void destroy_inodecache(void) { /* * Make sure all delayed rcu free inodes are flushed before we @@ -3708,7 +3707,7 @@ static void ext4_remove_li_request(struct ext4_li_request *elr) kfree(elr); } -static void ext4_unregister_li_request(struct super_block *sb) +void ext4_unregister_li_request(struct super_block *sb) { mutex_lock(&ext4_li_mtx); if (!ext4_li_info) { @@ -3722,6 +3721,22 @@ static void ext4_unregister_li_request(struct super_block *sb) mutex_unlock(&ext4_li_mtx); } +/* + * ext4_destroy_lazy_init() - Free lazy init info if no longer needed + * + * In U-Boot, there is no lazy init thread, so this must be called after + * ext4_unregister_li_request() to free ext4_li_info when the list is empty. + */ +void ext4_destroy_lazy_init(void) +{ + mutex_lock(&ext4_li_mtx); + if (ext4_li_info && list_empty(&ext4_li_info->li_request_list)) { + kfree(ext4_li_info); + ext4_li_info = NULL; + } + mutex_unlock(&ext4_li_mtx); +} + static struct task_struct *ext4_lazyinit_task; /* diff --git a/fs/ext4l/support.c b/fs/ext4l/support.c index 0f68746d99a..0d365e28265 100644 --- a/fs/ext4l/support.c +++ b/fs/ext4l/support.c @@ -897,3 +897,27 @@ void folio_get(struct folio *folio) if (folio) folio->_refcount++; } + +/** + * mapping_clear_folio_cache() - Release all folios in an address_space cache + * @mapping: The address_space to clear + * + * Releases the cache's reference to each folio. If no other references exist, + * the folio will be freed. + */ +void mapping_clear_folio_cache(struct address_space *mapping) +{ + int i; + + if (!mapping) + return; + + for (i = 0; i < mapping->folio_cache_count; i++) { + struct folio *folio = mapping->folio_cache[i]; + + if (folio) + folio_put(folio); + mapping->folio_cache[i] = NULL; + } + mapping->folio_cache_count = 0; +}