From patchwork Wed Dec 31 22:29:44 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1149 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=1767220278; bh=5Fv1+ir0Gf/HPKj2ggiRkrQ5wYCAR+vXphZ7oi/LliU=; 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=ZQ/3x3tLtHXmV2PnLLm7oZa4WKBM6nUFMEEOctig2gVeI1kRIF2j8Eq1ZieiFG9fS Kt8AoHs9oZwNHYMHjUM/mn1iJntJK/Dtz46pKEhDCJbL5asOszhaMbDSYJsQVYKfhq sdTooS2Ae7lCjUxIV0DHrIdhjtFsCm8s0fKOe9c1/T3xb4uuPw9zrUucW7zdF2wwQy kuy3k/dOgZwocurpsYSoF5Afs6RCifD5GoOpObbe7AAwyzPvfdWZKV/pwr3cAtvWp8 xZOQCtPeZm/r7RRBoVN7BCOxSVeZrfwW36EX1rUvQFoPX/2sUqMDlReda2cks3WYAH mVOqHMaZZzJ2g== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4F57C68FD3 for ; Wed, 31 Dec 2025 15:31: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 vG-B7ck7ubla for ; Wed, 31 Dec 2025 15:31:18 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767220278; bh=5Fv1+ir0Gf/HPKj2ggiRkrQ5wYCAR+vXphZ7oi/LliU=; 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=ZQ/3x3tLtHXmV2PnLLm7oZa4WKBM6nUFMEEOctig2gVeI1kRIF2j8Eq1ZieiFG9fS Kt8AoHs9oZwNHYMHjUM/mn1iJntJK/Dtz46pKEhDCJbL5asOszhaMbDSYJsQVYKfhq sdTooS2Ae7lCjUxIV0DHrIdhjtFsCm8s0fKOe9c1/T3xb4uuPw9zrUucW7zdF2wwQy kuy3k/dOgZwocurpsYSoF5Afs6RCifD5GoOpObbe7AAwyzPvfdWZKV/pwr3cAtvWp8 xZOQCtPeZm/r7RRBoVN7BCOxSVeZrfwW36EX1rUvQFoPX/2sUqMDlReda2cks3WYAH mVOqHMaZZzJ2g== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 3C97268FCB for ; Wed, 31 Dec 2025 15:31:18 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767220277; bh=SYaLloNQq+r9LPtJDoUDPkIQRFI0fIO8FTGa5YPHeiQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=CZpS3vRmie60LwgyXNgwfBDt9nZu+45jTyReV0ic5XO7ahIeBF8opE9PoKnA/xeWw 5dCKqXI0Im006LXSqPzWo5zkmNaXOK3QSRdQMi9ezBlolN+q1k+RGoswSjOlikqJz6 MyZze5V9Edb7wVDI/VHlCYH/hO0LhvC9eLEhWJ89mE7RgfHtr7q4w2C0TkLOCXom11 Q+upTOH+hyldOT68bjjjBTebW6xonqbLQ3womHdOmFh8cUlPYvIxB5H4ycn3YY1U5U YvpbUqvdkgOtkHmXXte1eiYcBM1Rda6TBAOruS2g05+7udKnrWv71c73tXbjBJ0a4M 5uzHsF7vpRJGg== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 8882368F65; Wed, 31 Dec 2025 15:31:17 -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 bEo1euPeLnuO; Wed, 31 Dec 2025 15:31:17 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1767220271; bh=ae3j/GT/z+YZeDCFWt53Ocz6iAZIYaalBHpkWIgzLrg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XReBrzI6TSvsEwDVBe4cT/Q/OrzVo4PRUIZPsDZyGFvGPFUbX+GxVMIcjoHsIKUZx DPpRiLm1yF7EM1p+w7scN3hWXp4GfrLxxm2/zvYb2cZapdjRDeoAJ9iF8jybELqF8v s0upPk4Vr5jfN6f7U8P+KIG9kdiIsLPZT3cX9YaO+0lJPSKlOfR02ETenNpOYOiSEZ 6Tmb2EYqnO40VP8gHjovXtMsxkGZ5bY7/MuAk6r3kvPBfNpSvQ2oLnPiJa+4jWGP63 pBggskZOz4B/GwH/dZGd5QEHUdhfiBeYm68eUnzqGFwGto07t1iYLkDon50t5GWlzq WpiSNErde7Q5w== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id 330F068C5D; Wed, 31 Dec 2025 15:31:11 -0700 (MST) From: Simon Glass To: U-Boot Concept Date: Wed, 31 Dec 2025 15:29:44 -0700 Message-ID: <20251231223008.3251711-12-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20251231223008.3251711-1-sjg@u-boot.org> References: <20251231223008.3251711-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: GJ7HJO5LZ7SUIS3B5UKJY435JDLADBQL X-Message-ID-Hash: GJ7HJO5LZ7SUIS3B5UKJY435JDLADBQL 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 Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 11/26] 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 Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- fs/ext4l/ext4.h | 6 ++ fs/ext4l/ext4_uboot.h | 1 + fs/ext4l/interface.c | 135 ++++++++++++++++++++++++++++++++++++++++++ fs/ext4l/super.c | 25 ++++++-- 4 files changed, 162 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 78d44faa0db..abe54e67aa4 100644 --- a/fs/ext4l/ext4_uboot.h +++ b/fs/ext4l/ext4_uboot.h @@ -2086,6 +2086,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/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; /*