diff mbox series

[3/6] xfs: remove xfs_ifree_local_data

Message ID 20200510072404.986627-4-hch@lst.de (mailing list archive)
State Superseded
Headers show
Series [1/6] xfs: use XFS_IFORK_BOFF xchk_bmap_check_rmaps | expand

Commit Message

Christoph Hellwig May 10, 2020, 7:24 a.m. UTC
xfs_ifree only need to free inline data in the data fork, as we've
already taken care of the attr fork before (and in fact freed the
fork structure).  Just open code the freeing of the inline data.

Signed-off-by: Christoph Hellwig <hch@lst.de>
---
 fs/xfs/xfs_inode.c | 30 ++++++++++--------------------
 1 file changed, 10 insertions(+), 20 deletions(-)

Comments

Chandan Babu R May 11, 2020, 4:32 p.m. UTC | #1
On Sunday 10 May 2020 12:54:01 PM IST Christoph Hellwig wrote:
> xfs_ifree only need to free inline data in the data fork, as we've
> already taken care of the attr fork before (and in fact freed the
> fork structure).  Just open code the freeing of the inline data.
>

xfs_inactive() => xfs_attr_inactive() would have freed the attr fork. Hence
the changes made in this patch are correct.

Reviewed-by: Chandan Babu R <chandanrlinux@gmail.com>

> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---
>  fs/xfs/xfs_inode.c | 30 ++++++++++--------------------
>  1 file changed, 10 insertions(+), 20 deletions(-)
> 
> diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
> index 549ff468b7b60..7d3144dc99b72 100644
> --- a/fs/xfs/xfs_inode.c
> +++ b/fs/xfs/xfs_inode.c
> @@ -2711,24 +2711,6 @@ xfs_ifree_cluster(
>  	return 0;
>  }
>  
> -/*
> - * Free any local-format buffers sitting around before we reset to
> - * extents format.
> - */
> -static inline void
> -xfs_ifree_local_data(
> -	struct xfs_inode	*ip,
> -	int			whichfork)
> -{
> -	struct xfs_ifork	*ifp;
> -
> -	if (XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_LOCAL)
> -		return;
> -
> -	ifp = XFS_IFORK_PTR(ip, whichfork);
> -	xfs_idata_realloc(ip, -ifp->if_bytes, whichfork);
> -}
> -
>  /*
>   * This is called to return an inode to the inode free list.
>   * The inode should already be truncated to 0 length and have
> @@ -2765,8 +2747,16 @@ xfs_ifree(
>  	if (error)
>  		return error;
>  
> -	xfs_ifree_local_data(ip, XFS_DATA_FORK);
> -	xfs_ifree_local_data(ip, XFS_ATTR_FORK);
> +	/*
> +	 * Free any local-format data sitting around before we reset the
> +	 * data fork to extents format.  Note that the attr fork data has
> +	 * already been freed by xfs_attr_inactive.
> +	 */
> +	if (ip->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
> +		kmem_free(ip->i_df.if_u1.if_data);
> +		ip->i_df.if_u1.if_data = NULL;
> +		ip->i_df.if_bytes = 0;
> +	}
>  
>  	VFS_I(ip)->i_mode = 0;		/* mark incore inode as free */
>  	ip->i_d.di_flags = 0;
>
Brian Foster May 12, 2020, 3:31 p.m. UTC | #2
On Sun, May 10, 2020 at 09:24:01AM +0200, Christoph Hellwig wrote:
> xfs_ifree only need to free inline data in the data fork, as we've
> already taken care of the attr fork before (and in fact freed the
> fork structure).  Just open code the freeing of the inline data.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---

Reviewed-by: Brian Foster <bfoster@redhat.com>

>  fs/xfs/xfs_inode.c | 30 ++++++++++--------------------
>  1 file changed, 10 insertions(+), 20 deletions(-)
> 
> diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
> index 549ff468b7b60..7d3144dc99b72 100644
> --- a/fs/xfs/xfs_inode.c
> +++ b/fs/xfs/xfs_inode.c
> @@ -2711,24 +2711,6 @@ xfs_ifree_cluster(
>  	return 0;
>  }
>  
> -/*
> - * Free any local-format buffers sitting around before we reset to
> - * extents format.
> - */
> -static inline void
> -xfs_ifree_local_data(
> -	struct xfs_inode	*ip,
> -	int			whichfork)
> -{
> -	struct xfs_ifork	*ifp;
> -
> -	if (XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_LOCAL)
> -		return;
> -
> -	ifp = XFS_IFORK_PTR(ip, whichfork);
> -	xfs_idata_realloc(ip, -ifp->if_bytes, whichfork);
> -}
> -
>  /*
>   * This is called to return an inode to the inode free list.
>   * The inode should already be truncated to 0 length and have
> @@ -2765,8 +2747,16 @@ xfs_ifree(
>  	if (error)
>  		return error;
>  
> -	xfs_ifree_local_data(ip, XFS_DATA_FORK);
> -	xfs_ifree_local_data(ip, XFS_ATTR_FORK);
> +	/*
> +	 * Free any local-format data sitting around before we reset the
> +	 * data fork to extents format.  Note that the attr fork data has
> +	 * already been freed by xfs_attr_inactive.
> +	 */
> +	if (ip->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
> +		kmem_free(ip->i_df.if_u1.if_data);
> +		ip->i_df.if_u1.if_data = NULL;
> +		ip->i_df.if_bytes = 0;
> +	}
>  
>  	VFS_I(ip)->i_mode = 0;		/* mark incore inode as free */
>  	ip->i_d.di_flags = 0;
> -- 
> 2.26.2
>
Darrick J. Wong May 16, 2020, 6:07 p.m. UTC | #3
On Sun, May 10, 2020 at 09:24:01AM +0200, Christoph Hellwig wrote:
> xfs_ifree only need to free inline data in the data fork, as we've
> already taken care of the attr fork before (and in fact freed the
> fork structure).  Just open code the freeing of the inline data.
> 
> Signed-off-by: Christoph Hellwig <hch@lst.de>

Looks ok,
Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>

--D

> ---
>  fs/xfs/xfs_inode.c | 30 ++++++++++--------------------
>  1 file changed, 10 insertions(+), 20 deletions(-)
> 
> diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
> index 549ff468b7b60..7d3144dc99b72 100644
> --- a/fs/xfs/xfs_inode.c
> +++ b/fs/xfs/xfs_inode.c
> @@ -2711,24 +2711,6 @@ xfs_ifree_cluster(
>  	return 0;
>  }
>  
> -/*
> - * Free any local-format buffers sitting around before we reset to
> - * extents format.
> - */
> -static inline void
> -xfs_ifree_local_data(
> -	struct xfs_inode	*ip,
> -	int			whichfork)
> -{
> -	struct xfs_ifork	*ifp;
> -
> -	if (XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_LOCAL)
> -		return;
> -
> -	ifp = XFS_IFORK_PTR(ip, whichfork);
> -	xfs_idata_realloc(ip, -ifp->if_bytes, whichfork);
> -}
> -
>  /*
>   * This is called to return an inode to the inode free list.
>   * The inode should already be truncated to 0 length and have
> @@ -2765,8 +2747,16 @@ xfs_ifree(
>  	if (error)
>  		return error;
>  
> -	xfs_ifree_local_data(ip, XFS_DATA_FORK);
> -	xfs_ifree_local_data(ip, XFS_ATTR_FORK);
> +	/*
> +	 * Free any local-format data sitting around before we reset the
> +	 * data fork to extents format.  Note that the attr fork data has
> +	 * already been freed by xfs_attr_inactive.
> +	 */
> +	if (ip->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
> +		kmem_free(ip->i_df.if_u1.if_data);
> +		ip->i_df.if_u1.if_data = NULL;
> +		ip->i_df.if_bytes = 0;
> +	}
>  
>  	VFS_I(ip)->i_mode = 0;		/* mark incore inode as free */
>  	ip->i_d.di_flags = 0;
> -- 
> 2.26.2
>
Alex Lyakas July 24, 2022, 12:14 p.m. UTC | #4
Hi Christoph,

On Sat, May 16, 2020 at 9:07 PM Darrick J. Wong <darrick.wong@oracle.com> wrote:
>
> On Sun, May 10, 2020 at 09:24:01AM +0200, Christoph Hellwig wrote:
> > xfs_ifree only need to free inline data in the data fork, as we've
> > already taken care of the attr fork before (and in fact freed the
> > fork structure).  Just open code the freeing of the inline data.
> >
> > Signed-off-by: Christoph Hellwig <hch@lst.de>
>
> Looks ok,
> Reviewed-by: Darrick J. Wong <darrick.wong@oracle.com>
>
> --D
>
> > ---
> >  fs/xfs/xfs_inode.c | 30 ++++++++++--------------------
> >  1 file changed, 10 insertions(+), 20 deletions(-)
> >
> > diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
> > index 549ff468b7b60..7d3144dc99b72 100644
> > --- a/fs/xfs/xfs_inode.c
> > +++ b/fs/xfs/xfs_inode.c
> > @@ -2711,24 +2711,6 @@ xfs_ifree_cluster(
> >       return 0;
> >  }
> >
> > -/*
> > - * Free any local-format buffers sitting around before we reset to
> > - * extents format.
> > - */
> > -static inline void
> > -xfs_ifree_local_data(
> > -     struct xfs_inode        *ip,
> > -     int                     whichfork)
> > -{
> > -     struct xfs_ifork        *ifp;
> > -
> > -     if (XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_LOCAL)
> > -             return;
> > -
> > -     ifp = XFS_IFORK_PTR(ip, whichfork);
> > -     xfs_idata_realloc(ip, -ifp->if_bytes, whichfork);
> > -}
> > -
> >  /*
> >   * This is called to return an inode to the inode free list.
> >   * The inode should already be truncated to 0 length and have
> > @@ -2765,8 +2747,16 @@ xfs_ifree(
> >       if (error)
> >               return error;
> >
> > -     xfs_ifree_local_data(ip, XFS_DATA_FORK);
> > -     xfs_ifree_local_data(ip, XFS_ATTR_FORK);
> > +     /*
> > +      * Free any local-format data sitting around before we reset the
> > +      * data fork to extents format.  Note that the attr fork data has
> > +      * already been freed by xfs_attr_inactive.
> > +      */
> > +     if (ip->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
> > +             kmem_free(ip->i_df.if_u1.if_data);
> > +             ip->i_df.if_u1.if_data = NULL;
> > +             ip->i_df.if_bytes = 0;
> > +     }
> >
> >       VFS_I(ip)->i_mode = 0;          /* mark incore inode as free */
> >       ip->i_d.di_flags = 0;
> > --
> > 2.26.2
> >


I stumbled upon this patch, by analyzing a kernel panic we had [1].
Looking at the call trace, the panic happened in
xfs_ifree_local_data() being called with XFS_ATTR_FORK.
It looks like
ifp = XFS_IFORK_PTR(ip, whichfork);
returned NULL.

Based on your patch, do I understand correctly that it fixes the panic?

What happened seems to be that inode had an attribute fork and
xfs_attr_fork_remove() was called on it. This function set:
ip->i_afp = NULL
ip->i_d.di_aformat = XFS_DINODE_FMT_EXTENTS.

As a result, xfs_ifree_local_data() [2] checked XFS_IFORK_FORMAT() and
got XFS_DINODE_FMT_EXTENTS. So it performed:
    ifp = XFS_IFORK_PTR(ip, whichfork);
    xfs_idata_realloc(ip, -ifp->if_bytes, whichfork);
which caused NULL pointer exception.

Is this analysis correct, that any time we have an inode with an
attribute fork, we will crash deleting it?

Thanks,
Alex.



[1]
BUG: unable to handle kernel NULL pointer dereference at           (null)
IP: xfs_ifree+0x12c/0x150 [xfs]
PGD 800000066eea0067 P4D 800000066eea0067 PUD 3066ce067 PMD 0
Oops: 0000 [#1] PREEMPT SMP PTI
Modules linked in: binfmt_misc xt_nat xfs(OE) sd_mod sg bonding xt_CHECKSUM i
 libiscsi_tcp(OE) sunrpc libiscsi(OE) scsi_transport_iscsi(OE) i6300esb ip_ta
CPU: 3 PID: 19176 Comm: swift-object-re Tainted: G        W  OE   4.14.99-zad
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 0
task: ffffa009b0d45640 task.stack: ffffb0a85dddc000
RIP: 0010:xfs_ifree+0x12c/0x150 [xfs]
RSP: 0018:ffffb0a85dddfdb8 EFLAGS: 00010246
RAX: 0000000000000000 RBX: ffffa00999610f00 RCX: 000000168215a803
RDX: 0000000000000000 RSI: ffffd0a83f9b3ed0 RDI: ffffa00999610f00
RBP: ffffa00edbe1af30 R08: 00003091b4833ed0 R09: ffffffffc0a39de4
R10: 0000000000000000 R11: 0000000000000040 R12: ffffb0a85dddfe10
R13: ffffb0a85dddfe08 R14: ffffa00d7f147780 R15: 0000000000000000
FS:  00007f012ffff700(0000) GS:ffffa0168b180000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000000000000 CR3: 0000000749f26006 CR4: 00000000003606e0
DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
Call Trace:
 xfs_inactive_ifree+0xbb/0x220 [xfs]
 xfs_inactive+0x74/0x100 [xfs]
 xfs_fs_destroy_inode+0xb4/0x240 [xfs]
 do_unlinkat+0x1b3/0x310
 do_syscall_64+0x6e/0x120
 entry_SYSCALL_64_after_hwframe+0x3d/0xa2


[2]
static inline void
xfs_ifree_local_data(
    struct xfs_inode    *ip,
    int            whichfork)
{
    struct xfs_ifork    *ifp;

    if (XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_LOCAL)
        return;

    ifp = XFS_IFORK_PTR(ip, whichfork);
    xfs_idata_realloc(ip, -ifp->if_bytes, whichfork);
}
diff mbox series

Patch

diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
index 549ff468b7b60..7d3144dc99b72 100644
--- a/fs/xfs/xfs_inode.c
+++ b/fs/xfs/xfs_inode.c
@@ -2711,24 +2711,6 @@  xfs_ifree_cluster(
 	return 0;
 }
 
-/*
- * Free any local-format buffers sitting around before we reset to
- * extents format.
- */
-static inline void
-xfs_ifree_local_data(
-	struct xfs_inode	*ip,
-	int			whichfork)
-{
-	struct xfs_ifork	*ifp;
-
-	if (XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_LOCAL)
-		return;
-
-	ifp = XFS_IFORK_PTR(ip, whichfork);
-	xfs_idata_realloc(ip, -ifp->if_bytes, whichfork);
-}
-
 /*
  * This is called to return an inode to the inode free list.
  * The inode should already be truncated to 0 length and have
@@ -2765,8 +2747,16 @@  xfs_ifree(
 	if (error)
 		return error;
 
-	xfs_ifree_local_data(ip, XFS_DATA_FORK);
-	xfs_ifree_local_data(ip, XFS_ATTR_FORK);
+	/*
+	 * Free any local-format data sitting around before we reset the
+	 * data fork to extents format.  Note that the attr fork data has
+	 * already been freed by xfs_attr_inactive.
+	 */
+	if (ip->i_d.di_format == XFS_DINODE_FMT_LOCAL) {
+		kmem_free(ip->i_df.if_u1.if_data);
+		ip->i_df.if_u1.if_data = NULL;
+		ip->i_df.if_bytes = 0;
+	}
 
 	VFS_I(ip)->i_mode = 0;		/* mark incore inode as free */
 	ip->i_d.di_flags = 0;