Re: [PATCH v2] ocfs2: fix hung task in orphan recovery

Joseph Qi <[email protected]> Thu, 2 Jul 2026 20:18:39 +0800
Newsgroups dev.linux.lists.ocfs2-devel,org.kernel.vger.linux-kernel
Message-ID <[email protected]>

On 7/2/26 5:05 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@mail.gmail.com/
> Assisted-by: Codex:gpt-5.5-xhigh
> Signed-off-by: Jiaming Zhang <[email protected]>

Looks fine.
Reviewed-by: Joseph Qi <[email protected]>
> ---
> Changes in v2:
> - Propagate the actual error from ocfs2_empty_dir() to caller.
> - Update ocfs2_unlink() and ocfs2_rename() to distinguish errors from a
>   non-empty directory result.
> 
>  fs/ocfs2/dir.c   | 20 ++++++++++++++------
>  fs/ocfs2/namei.c | 11 ++++++++---
>  2 files changed, 22 insertions(+), 9 deletions(-)
> 
> diff --git a/fs/ocfs2/dir.c b/fs/ocfs2/dir.c
> index 8e6b03238327..baf3eca7b4e4 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;
>  
>  	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;
>  		}
>  
> @@ -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);
>  }
>  
>  /*
> @@ -2167,7 +2171,7 @@ static int ocfs2_empty_dir_dx(struct inode *inode,
>  /*
>   * routine to check that the specified directory is empty (for rmdir)
>   *
> - * Returns 1 if dir is empty, zero otherwise.
> + * Returns 1 if dir is empty, zero if not, and a negative errno on error.
>   *
>   * XXX: This is a performance problem for unindexed directories.
>   */
> @@ -2180,8 +2184,10 @@ int ocfs2_empty_dir(struct inode *inode)
>  
>  	if (ocfs2_dir_indexed(inode)) {
>  		ret = ocfs2_empty_dir_dx(inode, &priv);
> -		if (ret)
> +		if (ret) {
>  			mlog_errno(ret);
> +			return ret;
> +		}
>  		/*
>  		 * We still run ocfs2_dir_foreach to get the checks
>  		 * for "." and "..".
> @@ -2189,8 +2195,10 @@ int ocfs2_empty_dir(struct inode *inode)
>  	}
>  
>  	ret = ocfs2_dir_foreach(inode, &priv.ctx);
> -	if (ret)
> +	if (ret) {
>  		mlog_errno(ret);
> +		return ret;
> +	}
>  
>  	if (!priv.seen_dot || !priv.seen_dot_dot) {
>  		mlog(ML_ERROR, "bad directory (dir #%llu) - no `.' or `..'\n",
> diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c
> index 1277666c77cd..abce59e1f180 100644
> --- a/fs/ocfs2/namei.c
> +++ b/fs/ocfs2/namei.c
> @@ -945,7 +945,10 @@ static int ocfs2_unlink(struct inode *dir,
>  	child_locked = 1;
>  
>  	if (S_ISDIR(inode->i_mode)) {
> -		if (inode->i_nlink != 2 || !ocfs2_empty_dir(inode)) {
> +		status = ocfs2_empty_dir(inode);
> +		if (status < 0)
> +			goto leave;
> +		if (inode->i_nlink != 2 || !status) {
>  			status = -ENOTEMPTY;
>  			goto leave;
>  		}
> @@ -1499,8 +1502,10 @@ static int ocfs2_rename(struct mnt_idmap *idmap,
>  
>  	if (target_exists) {
>  		if (S_ISDIR(new_inode->i_mode)) {
> -			if (new_inode->i_nlink != 2 ||
> -			    !ocfs2_empty_dir(new_inode)) {
> +			status = ocfs2_empty_dir(new_inode);
> +			if (status < 0)
> +				goto bail;
> +			if (new_inode->i_nlink != 2 || !status) {
>  				status = -ENOTEMPTY;
>  				goto bail;
>  			}