Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f86f4037 authored by Darrick J. Wong's avatar Darrick J. Wong
Browse files

xfs: teach get_bmapx about shared extents and the CoW fork



Teach xfs_getbmapx how to report shared extents and CoW fork contents
accurately in the bmap output by querying the refcount btree
appropriately.

Signed-off-by: default avatarDarrick J. Wong <darrick.wong@oracle.com>
Reviewed-by: default avatarChristoph Hellwig <hch@lst.de>
parent cc714660
Loading
Loading
Loading
Loading
+3 −1
Original line number Original line Diff line number Diff line
@@ -81,14 +81,16 @@ struct getbmapx {
#define BMV_IF_PREALLOC		0x4	/* rtn status BMV_OF_PREALLOC if req */
#define BMV_IF_PREALLOC		0x4	/* rtn status BMV_OF_PREALLOC if req */
#define BMV_IF_DELALLOC		0x8	/* rtn status BMV_OF_DELALLOC if req */
#define BMV_IF_DELALLOC		0x8	/* rtn status BMV_OF_DELALLOC if req */
#define BMV_IF_NO_HOLES		0x10	/* Do not return holes */
#define BMV_IF_NO_HOLES		0x10	/* Do not return holes */
#define BMV_IF_COWFORK		0x20	/* return CoW fork rather than data */
#define BMV_IF_VALID	\
#define BMV_IF_VALID	\
	(BMV_IF_ATTRFORK|BMV_IF_NO_DMAPI_READ|BMV_IF_PREALLOC|	\
	(BMV_IF_ATTRFORK|BMV_IF_NO_DMAPI_READ|BMV_IF_PREALLOC|	\
	 BMV_IF_DELALLOC|BMV_IF_NO_HOLES)
	 BMV_IF_DELALLOC|BMV_IF_NO_HOLES|BMV_IF_COWFORK)


/*	bmv_oflags values - returned for each non-header segment */
/*	bmv_oflags values - returned for each non-header segment */
#define BMV_OF_PREALLOC		0x1	/* segment = unwritten pre-allocation */
#define BMV_OF_PREALLOC		0x1	/* segment = unwritten pre-allocation */
#define BMV_OF_DELALLOC		0x2	/* segment = delayed allocation */
#define BMV_OF_DELALLOC		0x2	/* segment = delayed allocation */
#define BMV_OF_LAST		0x4	/* segment is the last in the file */
#define BMV_OF_LAST		0x4	/* segment is the last in the file */
#define BMV_OF_SHARED		0x8	/* segment shared with another file */


/*
/*
 * Structure for XFS_IOC_FSSETDM.
 * Structure for XFS_IOC_FSSETDM.
+140 −14
Original line number Original line Diff line number Diff line
@@ -42,6 +42,9 @@
#include "xfs_icache.h"
#include "xfs_icache.h"
#include "xfs_log.h"
#include "xfs_log.h"
#include "xfs_rmap_btree.h"
#include "xfs_rmap_btree.h"
#include "xfs_iomap.h"
#include "xfs_reflink.h"
#include "xfs_refcount.h"


/* Kernel only BMAP related definitions and functions */
/* Kernel only BMAP related definitions and functions */


@@ -389,11 +392,13 @@ xfs_bmap_count_blocks(
STATIC int
STATIC int
xfs_getbmapx_fix_eof_hole(
xfs_getbmapx_fix_eof_hole(
	xfs_inode_t		*ip,		/* xfs incore inode pointer */
	xfs_inode_t		*ip,		/* xfs incore inode pointer */
	int			whichfork,
	struct getbmapx		*out,		/* output structure */
	struct getbmapx		*out,		/* output structure */
	int			prealloced,	/* this is a file with
	int			prealloced,	/* this is a file with
						 * preallocated data space */
						 * preallocated data space */
	__int64_t		end,		/* last block requested */
	__int64_t		end,		/* last block requested */
	xfs_fsblock_t		startblock)
	xfs_fsblock_t		startblock,
	bool			moretocome)
{
{
	__int64_t		fixlen;
	__int64_t		fixlen;
	xfs_mount_t		*mp;		/* file system mount point */
	xfs_mount_t		*mp;		/* file system mount point */
@@ -418,8 +423,9 @@ xfs_getbmapx_fix_eof_hole(
		else
		else
			out->bmv_block = xfs_fsb_to_db(ip, startblock);
			out->bmv_block = xfs_fsb_to_db(ip, startblock);
		fileblock = XFS_BB_TO_FSB(ip->i_mount, out->bmv_offset);
		fileblock = XFS_BB_TO_FSB(ip->i_mount, out->bmv_offset);
		ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK);
		ifp = XFS_IFORK_PTR(ip, whichfork);
		if (xfs_iext_bno_to_ext(ifp, fileblock, &lastx) &&
		if (!moretocome &&
		    xfs_iext_bno_to_ext(ifp, fileblock, &lastx) &&
		   (lastx == (ifp->if_bytes / (uint)sizeof(xfs_bmbt_rec_t))-1))
		   (lastx == (ifp->if_bytes / (uint)sizeof(xfs_bmbt_rec_t))-1))
			out->bmv_oflags |= BMV_OF_LAST;
			out->bmv_oflags |= BMV_OF_LAST;
	}
	}
@@ -427,6 +433,81 @@ xfs_getbmapx_fix_eof_hole(
	return 1;
	return 1;
}
}


/* Adjust the reported bmap around shared/unshared extent transitions. */
STATIC int
xfs_getbmap_adjust_shared(
	struct xfs_inode		*ip,
	int				whichfork,
	struct xfs_bmbt_irec		*map,
	struct getbmapx			*out,
	struct xfs_bmbt_irec		*next_map)
{
	struct xfs_mount		*mp = ip->i_mount;
	xfs_agnumber_t			agno;
	xfs_agblock_t			agbno;
	xfs_agblock_t			ebno;
	xfs_extlen_t			elen;
	xfs_extlen_t			nlen;
	int				error;

	next_map->br_startblock = NULLFSBLOCK;
	next_map->br_startoff = NULLFILEOFF;
	next_map->br_blockcount = 0;

	/* Only written data blocks can be shared. */
	if (!xfs_is_reflink_inode(ip) || whichfork != XFS_DATA_FORK ||
	    map->br_startblock == DELAYSTARTBLOCK ||
	    map->br_startblock == HOLESTARTBLOCK ||
	    ISUNWRITTEN(map))
		return 0;

	agno = XFS_FSB_TO_AGNO(mp, map->br_startblock);
	agbno = XFS_FSB_TO_AGBNO(mp, map->br_startblock);
	error = xfs_reflink_find_shared(mp, agno, agbno, map->br_blockcount,
			&ebno, &elen, true);
	if (error)
		return error;

	if (ebno == NULLAGBLOCK) {
		/* No shared blocks at all. */
		return 0;
	} else if (agbno == ebno) {
		/*
		 * Shared extent at (agbno, elen).  Shrink the reported
		 * extent length and prepare to move the start of map[i]
		 * to agbno+elen, with the aim of (re)formatting the new
		 * map[i] the next time through the inner loop.
		 */
		out->bmv_length = XFS_FSB_TO_BB(mp, elen);
		out->bmv_oflags |= BMV_OF_SHARED;
		if (elen != map->br_blockcount) {
			*next_map = *map;
			next_map->br_startblock += elen;
			next_map->br_startoff += elen;
			next_map->br_blockcount -= elen;
		}
		map->br_blockcount -= elen;
	} else {
		/*
		 * There's an unshared extent (agbno, ebno - agbno)
		 * followed by shared extent at (ebno, elen).  Shrink
		 * the reported extent length to cover only the unshared
		 * extent and prepare to move up the start of map[i] to
		 * ebno, with the aim of (re)formatting the new map[i]
		 * the next time through the inner loop.
		 */
		*next_map = *map;
		nlen = ebno - agbno;
		out->bmv_length = XFS_FSB_TO_BB(mp, nlen);
		next_map->br_startblock += nlen;
		next_map->br_startoff += nlen;
		next_map->br_blockcount -= nlen;
		map->br_blockcount -= nlen;
	}

	return 0;
}

/*
/*
 * Get inode's extents as described in bmv, and format for output.
 * Get inode's extents as described in bmv, and format for output.
 * Calls formatter to fill the user's buffer until all extents
 * Calls formatter to fill the user's buffer until all extents
@@ -459,12 +540,28 @@ xfs_getbmap(
	int			iflags;		/* interface flags */
	int			iflags;		/* interface flags */
	int			bmapi_flags;	/* flags for xfs_bmapi */
	int			bmapi_flags;	/* flags for xfs_bmapi */
	int			cur_ext = 0;
	int			cur_ext = 0;
	struct xfs_bmbt_irec	inject_map;


	mp = ip->i_mount;
	mp = ip->i_mount;
	iflags = bmv->bmv_iflags;
	iflags = bmv->bmv_iflags;
	whichfork = iflags & BMV_IF_ATTRFORK ? XFS_ATTR_FORK : XFS_DATA_FORK;


	if (whichfork == XFS_ATTR_FORK) {
#ifndef DEBUG
	/* Only allow CoW fork queries if we're debugging. */
	if (iflags & BMV_IF_COWFORK)
		return -EINVAL;
#endif
	if ((iflags & BMV_IF_ATTRFORK) && (iflags & BMV_IF_COWFORK))
		return -EINVAL;

	if (iflags & BMV_IF_ATTRFORK)
		whichfork = XFS_ATTR_FORK;
	else if (iflags & BMV_IF_COWFORK)
		whichfork = XFS_COW_FORK;
	else
		whichfork = XFS_DATA_FORK;

	switch (whichfork) {
	case XFS_ATTR_FORK:
		if (XFS_IFORK_Q(ip)) {
		if (XFS_IFORK_Q(ip)) {
			if (ip->i_d.di_aformat != XFS_DINODE_FMT_EXTENTS &&
			if (ip->i_d.di_aformat != XFS_DINODE_FMT_EXTENTS &&
			    ip->i_d.di_aformat != XFS_DINODE_FMT_BTREE &&
			    ip->i_d.di_aformat != XFS_DINODE_FMT_BTREE &&
@@ -480,7 +577,15 @@ xfs_getbmap(


		prealloced = 0;
		prealloced = 0;
		fixlen = 1LL << 32;
		fixlen = 1LL << 32;
	} else {
		break;
	case XFS_COW_FORK:
		if (ip->i_cformat != XFS_DINODE_FMT_EXTENTS)
			return -EINVAL;

		prealloced = 0;
		fixlen = XFS_ISIZE(ip);
		break;
	default:
		if (ip->i_d.di_format != XFS_DINODE_FMT_EXTENTS &&
		if (ip->i_d.di_format != XFS_DINODE_FMT_EXTENTS &&
		    ip->i_d.di_format != XFS_DINODE_FMT_BTREE &&
		    ip->i_d.di_format != XFS_DINODE_FMT_BTREE &&
		    ip->i_d.di_format != XFS_DINODE_FMT_LOCAL)
		    ip->i_d.di_format != XFS_DINODE_FMT_LOCAL)
@@ -494,6 +599,7 @@ xfs_getbmap(
			prealloced = 0;
			prealloced = 0;
			fixlen = XFS_ISIZE(ip);
			fixlen = XFS_ISIZE(ip);
		}
		}
		break;
	}
	}


	if (bmv->bmv_length == -1) {
	if (bmv->bmv_length == -1) {
@@ -520,7 +626,8 @@ xfs_getbmap(
		return -ENOMEM;
		return -ENOMEM;


	xfs_ilock(ip, XFS_IOLOCK_SHARED);
	xfs_ilock(ip, XFS_IOLOCK_SHARED);
	if (whichfork == XFS_DATA_FORK) {
	switch (whichfork) {
	case XFS_DATA_FORK:
		if (!(iflags & BMV_IF_DELALLOC) &&
		if (!(iflags & BMV_IF_DELALLOC) &&
		    (ip->i_delayed_blks || XFS_ISIZE(ip) > ip->i_d.di_size)) {
		    (ip->i_delayed_blks || XFS_ISIZE(ip) > ip->i_d.di_size)) {
			error = filemap_write_and_wait(VFS_I(ip)->i_mapping);
			error = filemap_write_and_wait(VFS_I(ip)->i_mapping);
@@ -538,8 +645,14 @@ xfs_getbmap(
		}
		}


		lock = xfs_ilock_data_map_shared(ip);
		lock = xfs_ilock_data_map_shared(ip);
	} else {
		break;
	case XFS_COW_FORK:
		lock = XFS_ILOCK_SHARED;
		xfs_ilock(ip, lock);
		break;
	case XFS_ATTR_FORK:
		lock = xfs_ilock_attr_map_shared(ip);
		lock = xfs_ilock_attr_map_shared(ip);
		break;
	}
	}


	/*
	/*
@@ -581,7 +694,8 @@ xfs_getbmap(
			goto out_free_map;
			goto out_free_map;
		ASSERT(nmap <= subnex);
		ASSERT(nmap <= subnex);


		for (i = 0; i < nmap && nexleft && bmv->bmv_length; i++) {
		for (i = 0; i < nmap && nexleft && bmv->bmv_length &&
				cur_ext < bmv->bmv_count; i++) {
			out[cur_ext].bmv_oflags = 0;
			out[cur_ext].bmv_oflags = 0;
			if (map[i].br_state == XFS_EXT_UNWRITTEN)
			if (map[i].br_state == XFS_EXT_UNWRITTEN)
				out[cur_ext].bmv_oflags |= BMV_OF_PREALLOC;
				out[cur_ext].bmv_oflags |= BMV_OF_PREALLOC;
@@ -614,9 +728,16 @@ xfs_getbmap(
				goto out_free_map;
				goto out_free_map;
			}
			}


			if (!xfs_getbmapx_fix_eof_hole(ip, &out[cur_ext],
			/* Is this a shared block? */
					prealloced, bmvend,
			error = xfs_getbmap_adjust_shared(ip, whichfork,
					map[i].br_startblock))
					&map[i], &out[cur_ext], &inject_map);
			if (error)
				goto out_free_map;

			if (!xfs_getbmapx_fix_eof_hole(ip, whichfork,
					&out[cur_ext], prealloced, bmvend,
					map[i].br_startblock,
					inject_map.br_startblock != NULLFSBLOCK))
				goto out_free_map;
				goto out_free_map;


			bmv->bmv_offset =
			bmv->bmv_offset =
@@ -636,11 +757,16 @@ xfs_getbmap(
				continue;
				continue;
			}
			}


			if (inject_map.br_startblock != NULLFSBLOCK) {
				map[i] = inject_map;
				i--;
			} else
				nexleft--;
				nexleft--;
			bmv->bmv_entries++;
			bmv->bmv_entries++;
			cur_ext++;
			cur_ext++;
		}
		}
	} while (nmap && nexleft && bmv->bmv_length);
	} while (nmap && nexleft && bmv->bmv_length &&
		 cur_ext < bmv->bmv_count);


 out_free_map:
 out_free_map:
	kmem_free(map);
	kmem_free(map);