Re: [PATCH] ocfs2: fix hung task in orphan recovery
From: Joseph Qi
Date: Wed Jul 01 2026 - 23:55:34 EST
On 7/1/26 5:16 PM, Jiaming Zhang wrote:
> A crafted OCFS2 image with corrupted orphan-directory extent metadata can make
> umount hang.
>
> During unmount, ocfs2_recovery_disable() waits for the ocfs2_complete_recovery
> work item to finish. The worker scans the orphan directory through
> ocfs2_queue_orphans() and ocfs2_dir_foreach(). If ocfs2_read_dir_block() fails
> on a corrupted directory block, ocfs2_dir_foreach_blk_el() skips the block and
> continues walking. On a badly corrupted directory this can keep orphan recovery
> busy for a long time, leaving umount blocked while flushing osb->ocfs2_wq.
>
> Return the read error immediately for full directory scans and propagate the
> error from ocfs2_dir_foreach(). When ocfs2_empty_dir() receives such an error,
> report the directory as non-empty so unlink/rmdir does not proceed on an
> unreadable directory.
>
> Closes: https://lore.kernel.org/lkml/CANypQFbWH76Y6LWHEwAvTP7aQL04uMJ=dDyL6YDmxa3fv3Tyjg@xxxxxxxxxxxxxx/
> Assisted-by: Codex:gpt-5.5-xhigh
> Signed-off-by: Jiaming Zhang <r772577952@xxxxxxxxx>
> ---
> fs/ocfs2/dir.c | 16 +++++++++++-----
> 1 file changed, 11 insertions(+), 5 deletions(-)
>
> diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
> index 8e6b03238327..9bebf28f4b77 100644
> --- a/fs/ocfs2/dir.c
> +++ b/fs/ocfs2/dir.c
> @@ -1866,6 +1866,7 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
> struct super_block * sb = inode->i_sb;
> unsigned int ra_sectors = 16;
> int stored = 0;
> + int ret = 0;
>
> bh = NULL;
>
> @@ -1873,9 +1874,13 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
>
> while (ctx->pos < i_size_read(inode)) {
> blk = ctx->pos >> sb->s_blocksize_bits;
> - if (ocfs2_read_dir_block(inode, blk, &bh, 0)) {
> + ret = ocfs2_read_dir_block(inode, blk, &bh, 0);
> + if (ret) {
> + if (persist)
> + return ret;
> /* Skip the corrupt dirblock and keep trying */
> ctx->pos += sb->s_blocksize - offset;
> + offset = 0;
> continue;
> }
>
> @@ -1950,7 +1955,7 @@ static int ocfs2_dir_foreach_blk_el(struct inode *inode,
> if (!persist && stored)
> break;
> }
> - return 0;
> + return ret;
> }
>
> static int ocfs2_dir_foreach_blk(struct inode *inode, u64 *f_version,
> @@ -1969,8 +1974,7 @@ static int ocfs2_dir_foreach_blk(struct inode *inode, u64 *f_version,
> int ocfs2_dir_foreach(struct inode *inode, struct dir_context *ctx)
> {
> u64 version = inode_query_iversion(inode);
> - ocfs2_dir_foreach_blk(inode, &version, ctx, true);
> - return 0;
> + return ocfs2_dir_foreach_blk(inode, &version, ctx, true);
> }
>
> /*
> @@ -2189,8 +2193,10 @@ int ocfs2_empty_dir(struct inode *inode)
> }
>
> ret = ocfs2_dir_foreach(inode, &priv.ctx);
> - if (ret)
> + if (ret) {
> mlog_errno(ret);
> + return 0;
Here returns 0 when a read fails, and it then converts to -ENOTEMPTY.
It seems we'd better not swallow the real error. So a cleaner approach
would be to propagate the actual error. And caller distinguish it like:
status = ocfs2_empty_dir(inode);
if (status < 0)
goto leave;
if (inode->i_nlink != 2 || !status) {
status = -ENOTEMPTY;
goto leave;
}
Thanks,
Joseph
> + }
>
> if (!priv.seen_dot || !priv.seen_dot_dot) {
> mlog(ML_ERROR, "bad directory (dir #%llu) - no `.' or `..'\n",