diff options
Diffstat (limited to 'fs/xfs/xfs_bmap.c')
| -rw-r--r-- | fs/xfs/xfs_bmap.c | 512 | 
1 files changed, 380 insertions, 132 deletions
diff --git a/fs/xfs/xfs_bmap.c b/fs/xfs/xfs_bmap.c index f47e65c30be..75c3fe5f3d9 100644 --- a/fs/xfs/xfs_bmap.c +++ b/fs/xfs/xfs_bmap.c @@ -17,39 +17,37 @@   */  #include "xfs.h"  #include "xfs_fs.h" +#include "xfs_shared.h"  #include "xfs_format.h" +#include "xfs_log_format.h" +#include "xfs_trans_resv.h"  #include "xfs_bit.h" -#include "xfs_log.h"  #include "xfs_inum.h" -#include "xfs_trans.h"  #include "xfs_sb.h"  #include "xfs_ag.h"  #include "xfs_mount.h" +#include "xfs_da_format.h"  #include "xfs_da_btree.h" -#include "xfs_dir2_format.h"  #include "xfs_dir2.h" -#include "xfs_bmap_btree.h" -#include "xfs_alloc_btree.h" -#include "xfs_ialloc_btree.h" -#include "xfs_dinode.h"  #include "xfs_inode.h"  #include "xfs_btree.h" -#include "xfs_mount.h" -#include "xfs_itable.h" +#include "xfs_trans.h"  #include "xfs_inode_item.h"  #include "xfs_extfree_item.h"  #include "xfs_alloc.h"  #include "xfs_bmap.h"  #include "xfs_bmap_util.h" +#include "xfs_bmap_btree.h"  #include "xfs_rtalloc.h"  #include "xfs_error.h" -#include "xfs_attr_leaf.h"  #include "xfs_quota.h"  #include "xfs_trans_space.h"  #include "xfs_buf_item.h" -#include "xfs_filestream.h"  #include "xfs_trace.h"  #include "xfs_symlink.h" +#include "xfs_attr_leaf.h" +#include "xfs_dinode.h" +#include "xfs_filestream.h"  kmem_zone_t		*xfs_bmap_free_item_zone; @@ -96,7 +94,7 @@ xfs_bmap_compute_maxlevels(  		maxleafents = MAXAEXTNUM;  		sz = XFS_BMDR_SPACE_CALC(MINABTPTRS);  	} -	maxrootrecs = xfs_bmdr_maxrecs(mp, sz, 0); +	maxrootrecs = xfs_bmdr_maxrecs(sz, 0);  	minleafrecs = mp->m_bmap_dmnr[0];  	minnoderecs = mp->m_bmap_dmnr[1];  	maxblocks = (maxleafents + minleafrecs - 1) / minleafrecs; @@ -235,7 +233,6 @@ xfs_default_attroffset(   */  STATIC void  xfs_bmap_forkoff_reset( -	xfs_mount_t	*mp,  	xfs_inode_t	*ip,  	int		whichfork)  { @@ -907,7 +904,7 @@ xfs_bmap_local_to_extents_empty(  	ASSERT(ifp->if_bytes == 0);  	ASSERT(XFS_IFORK_NEXTENTS(ip, whichfork) == 0); -	xfs_bmap_forkoff_reset(ip->i_mount, ip, whichfork); +	xfs_bmap_forkoff_reset(ip, whichfork);  	ifp->if_flags &= ~XFS_IFINLINE;  	ifp->if_flags |= XFS_IFEXTENTS;  	XFS_IFORK_FMT_SET(ip, whichfork, XFS_DINODE_FMT_EXTENTS); @@ -1101,10 +1098,11 @@ xfs_bmap_add_attrfork_local(  	if (S_ISDIR(ip->i_d.di_mode)) {  		memset(&dargs, 0, sizeof(dargs)); +		dargs.geo = ip->i_mount->m_dir_geo;  		dargs.dp = ip;  		dargs.firstblock = firstblock;  		dargs.flist = flist; -		dargs.total = ip->i_mount->m_dirblkfsbs; +		dargs.total = dargs.geo->fsbcount;  		dargs.whichfork = XFS_DATA_FORK;  		dargs.trans = tp;  		return xfs_dir2_sf_to_block(&dargs); @@ -1139,6 +1137,7 @@ xfs_bmap_add_attrfork(  	int			committed;	/* xaction was committed */  	int			logflags;	/* logging flags */  	int			error;		/* error return value */ +	int			cancel_flags = 0;  	ASSERT(XFS_IFORK_Q(ip) == 0); @@ -1149,19 +1148,20 @@ xfs_bmap_add_attrfork(  	if (rsvd)  		tp->t_flags |= XFS_TRANS_RESERVE;  	error = xfs_trans_reserve(tp, &M_RES(mp)->tr_addafork, blks, 0); -	if (error) -		goto error0; +	if (error) { +		xfs_trans_cancel(tp, 0); +		return error; +	} +	cancel_flags = XFS_TRANS_RELEASE_LOG_RES;  	xfs_ilock(ip, XFS_ILOCK_EXCL);  	error = xfs_trans_reserve_quota_nblks(tp, ip, blks, 0, rsvd ?  			XFS_QMOPT_RES_REGBLKS | XFS_QMOPT_FORCE_RES :  			XFS_QMOPT_RES_REGBLKS); -	if (error) { -		xfs_iunlock(ip, XFS_ILOCK_EXCL); -		xfs_trans_cancel(tp, XFS_TRANS_RELEASE_LOG_RES); -		return error; -	} +	if (error) +		goto trans_cancel; +	cancel_flags |= XFS_TRANS_ABORT;  	if (XFS_IFORK_Q(ip)) -		goto error1; +		goto trans_cancel;  	if (ip->i_d.di_aformat != XFS_DINODE_FMT_EXTENTS) {  		/*  		 * For inodes coming from pre-6.2 filesystems. @@ -1171,7 +1171,7 @@ xfs_bmap_add_attrfork(  	}  	ASSERT(ip->i_d.di_anextents == 0); -	xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL); +	xfs_trans_ijoin(tp, ip, 0);  	xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);  	switch (ip->i_d.di_format) { @@ -1193,7 +1193,7 @@ xfs_bmap_add_attrfork(  	default:  		ASSERT(0);  		error = XFS_ERROR(EINVAL); -		goto error1; +		goto trans_cancel;  	}  	ASSERT(ip->i_afp == NULL); @@ -1221,7 +1221,7 @@ xfs_bmap_add_attrfork(  	if (logflags)  		xfs_trans_log_inode(tp, ip, logflags);  	if (error) -		goto error2; +		goto bmap_cancel;  	if (!xfs_sb_version_hasattr(&mp->m_sb) ||  	   (!xfs_sb_version_hasattr2(&mp->m_sb) && version == 2)) {  		__int64_t sbfields = 0; @@ -1244,14 +1244,16 @@ xfs_bmap_add_attrfork(  	error = xfs_bmap_finish(&tp, &flist, &committed);  	if (error) -		goto error2; -	return xfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES); -error2: +		goto bmap_cancel; +	error = xfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES); +	xfs_iunlock(ip, XFS_ILOCK_EXCL); +	return error; + +bmap_cancel:  	xfs_bmap_cancel(&flist); -error1: +trans_cancel: +	xfs_trans_cancel(tp, cancel_flags);  	xfs_iunlock(ip, XFS_ILOCK_EXCL); -error0: -	xfs_trans_cancel(tp, XFS_TRANS_RELEASE_LOG_RES|XFS_TRANS_ABORT);  	return error;  } @@ -1482,7 +1484,7 @@ xfs_bmap_search_extents(  		xfs_alert_tag(ip->i_mount, XFS_PTAG_FSBLOCK_ZERO,  				"Access to block zero in inode %llu "  				"start_block: %llx start_off: %llx " -				"blkcnt: %llx extent-state: %x lastx: %x\n", +				"blkcnt: %llx extent-state: %x lastx: %x",  			(unsigned long long)ip->i_ino,  			(unsigned long long)gotp->br_startblock,  			(unsigned long long)gotp->br_startoff, @@ -1633,7 +1635,7 @@ xfs_bmap_last_extent(   * blocks at the end of the file which do not start at the previous data block,   * we will try to align the new blocks at stripe unit boundaries.   * - * Returns 0 in bma->aeof if the file (fork) is empty as any new write will be + * Returns 1 in bma->aeof if the file (fork) is empty as any new write will be   * at, or past the EOF.   */  STATIC int @@ -1648,9 +1650,14 @@ xfs_bmap_isaeof(  	bma->aeof = 0;  	error = xfs_bmap_last_extent(NULL, bma->ip, whichfork, &rec,  				     &is_empty); -	if (error || is_empty) +	if (error)  		return error; +	if (is_empty) { +		bma->aeof = 1; +		return 0; +	} +  	/*  	 * Check if we are allocation or past the last extent, or at least into  	 * the last delayed allocated extent. @@ -1668,7 +1675,6 @@ xfs_bmap_isaeof(   */  int  xfs_bmap_last_offset( -	struct xfs_trans	*tp,  	struct xfs_inode	*ip,  	xfs_fileoff_t		*last_block,  	int			whichfork) @@ -3510,6 +3516,67 @@ xfs_bmap_adjacent(  #undef ISVALID  } +static int +xfs_bmap_longest_free_extent( +	struct xfs_trans	*tp, +	xfs_agnumber_t		ag, +	xfs_extlen_t		*blen, +	int			*notinit) +{ +	struct xfs_mount	*mp = tp->t_mountp; +	struct xfs_perag	*pag; +	xfs_extlen_t		longest; +	int			error = 0; + +	pag = xfs_perag_get(mp, ag); +	if (!pag->pagf_init) { +		error = xfs_alloc_pagf_init(mp, tp, ag, XFS_ALLOC_FLAG_TRYLOCK); +		if (error) +			goto out; + +		if (!pag->pagf_init) { +			*notinit = 1; +			goto out; +		} +	} + +	longest = xfs_alloc_longest_free_extent(mp, pag); +	if (*blen < longest) +		*blen = longest; + +out: +	xfs_perag_put(pag); +	return error; +} + +static void +xfs_bmap_select_minlen( +	struct xfs_bmalloca	*ap, +	struct xfs_alloc_arg	*args, +	xfs_extlen_t		*blen, +	int			notinit) +{ +	if (notinit || *blen < ap->minlen) { +		/* +		 * Since we did a BUF_TRYLOCK above, it is possible that +		 * there is space for this request. +		 */ +		args->minlen = ap->minlen; +	} else if (*blen < args->maxlen) { +		/* +		 * If the best seen length is less than the request length, +		 * use the best as the minimum. +		 */ +		args->minlen = *blen; +	} else { +		/* +		 * Otherwise we've seen an extent as big as maxlen, use that +		 * as the minimum. +		 */ +		args->minlen = args->maxlen; +	} +} +  STATIC int  xfs_bmap_btalloc_nullfb(  	struct xfs_bmalloca	*ap, @@ -3517,111 +3584,74 @@ xfs_bmap_btalloc_nullfb(  	xfs_extlen_t		*blen)  {  	struct xfs_mount	*mp = ap->ip->i_mount; -	struct xfs_perag	*pag;  	xfs_agnumber_t		ag, startag;  	int			notinit = 0;  	int			error; -	if (ap->userdata && xfs_inode_is_filestream(ap->ip)) -		args->type = XFS_ALLOCTYPE_NEAR_BNO; -	else -		args->type = XFS_ALLOCTYPE_START_BNO; +	args->type = XFS_ALLOCTYPE_START_BNO;  	args->total = ap->total; -	/* -	 * Search for an allocation group with a single extent large enough -	 * for the request.  If one isn't found, then adjust the minimum -	 * allocation size to the largest space found. -	 */  	startag = ag = XFS_FSB_TO_AGNO(mp, args->fsbno);  	if (startag == NULLAGNUMBER)  		startag = ag = 0; -	pag = xfs_perag_get(mp, ag);  	while (*blen < args->maxlen) { -		if (!pag->pagf_init) { -			error = xfs_alloc_pagf_init(mp, args->tp, ag, -						    XFS_ALLOC_FLAG_TRYLOCK); -			if (error) { -				xfs_perag_put(pag); -				return error; -			} -		} - -		/* -		 * See xfs_alloc_fix_freelist... -		 */ -		if (pag->pagf_init) { -			xfs_extlen_t	longest; -			longest = xfs_alloc_longest_free_extent(mp, pag); -			if (*blen < longest) -				*blen = longest; -		} else -			notinit = 1; - -		if (xfs_inode_is_filestream(ap->ip)) { -			if (*blen >= args->maxlen) -				break; - -			if (ap->userdata) { -				/* -				 * If startag is an invalid AG, we've -				 * come here once before and -				 * xfs_filestream_new_ag picked the -				 * best currently available. -				 * -				 * Don't continue looping, since we -				 * could loop forever. -				 */ -				if (startag == NULLAGNUMBER) -					break; - -				error = xfs_filestream_new_ag(ap, &ag); -				xfs_perag_put(pag); -				if (error) -					return error; +		error = xfs_bmap_longest_free_extent(args->tp, ag, blen, +						     ¬init); +		if (error) +			return error; -				/* loop again to set 'blen'*/ -				startag = NULLAGNUMBER; -				pag = xfs_perag_get(mp, ag); -				continue; -			} -		}  		if (++ag == mp->m_sb.sb_agcount)  			ag = 0;  		if (ag == startag)  			break; -		xfs_perag_put(pag); -		pag = xfs_perag_get(mp, ag);  	} -	xfs_perag_put(pag); -	/* -	 * Since the above loop did a BUF_TRYLOCK, it is -	 * possible that there is space for this request. -	 */ -	if (notinit || *blen < ap->minlen) -		args->minlen = ap->minlen; -	/* -	 * If the best seen length is less than the request -	 * length, use the best as the minimum. -	 */ -	else if (*blen < args->maxlen) -		args->minlen = *blen; -	/* -	 * Otherwise we've seen an extent as big as maxlen, -	 * use that as the minimum. -	 */ -	else -		args->minlen = args->maxlen; +	xfs_bmap_select_minlen(ap, args, blen, notinit); +	return 0; +} + +STATIC int +xfs_bmap_btalloc_filestreams( +	struct xfs_bmalloca	*ap, +	struct xfs_alloc_arg	*args, +	xfs_extlen_t		*blen) +{ +	struct xfs_mount	*mp = ap->ip->i_mount; +	xfs_agnumber_t		ag; +	int			notinit = 0; +	int			error; + +	args->type = XFS_ALLOCTYPE_NEAR_BNO; +	args->total = ap->total; + +	ag = XFS_FSB_TO_AGNO(mp, args->fsbno); +	if (ag == NULLAGNUMBER) +		ag = 0; + +	error = xfs_bmap_longest_free_extent(args->tp, ag, blen, ¬init); +	if (error) +		return error; + +	if (*blen < args->maxlen) { +		error = xfs_filestream_new_ag(ap, &ag); +		if (error) +			return error; + +		error = xfs_bmap_longest_free_extent(args->tp, ag, blen, +						     ¬init); +		if (error) +			return error; + +	} + +	xfs_bmap_select_minlen(ap, args, blen, notinit);  	/* -	 * set the failure fallback case to look in the selected -	 * AG as the stream may have moved. +	 * Set the failure fallback case to look in the selected AG as stream +	 * may have moved.  	 */ -	if (xfs_inode_is_filestream(ap->ip)) -		ap->blkno = args->fsbno = XFS_AGB_TO_FSB(mp, ag, 0); - +	ap->blkno = args->fsbno = XFS_AGB_TO_FSB(mp, ag, 0);  	return 0;  } @@ -3641,10 +3671,19 @@ xfs_bmap_btalloc(  	int		isaligned;  	int		tryagain;  	int		error; +	int		stripe_align;  	ASSERT(ap->length);  	mp = ap->ip->i_mount; + +	/* stripe alignment for allocation is determined by mount parameters */ +	stripe_align = 0; +	if (mp->m_swidth && (mp->m_flags & XFS_MOUNT_SWALLOC)) +		stripe_align = mp->m_swidth; +	else if (mp->m_dalign) +		stripe_align = mp->m_dalign; +  	align = ap->userdata ? xfs_get_extsz_hint(ap->ip) : 0;  	if (unlikely(align)) {  		error = xfs_bmap_extsize_align(mp, &ap->got, &ap->prev, @@ -3653,6 +3692,8 @@ xfs_bmap_btalloc(  		ASSERT(!error);  		ASSERT(ap->length);  	} + +  	nullfb = *ap->firstblock == NULLFSBLOCK;  	fb_agno = nullfb ? NULLAGNUMBER : XFS_FSB_TO_AGNO(mp, *ap->firstblock);  	if (nullfb) { @@ -3690,7 +3731,15 @@ xfs_bmap_btalloc(  	args.firstblock = *ap->firstblock;  	blen = 0;  	if (nullfb) { -		error = xfs_bmap_btalloc_nullfb(ap, &args, &blen); +		/* +		 * Search for an allocation group with a single extent large +		 * enough for the request.  If one isn't found, then adjust +		 * the minimum allocation size to the largest space found. +		 */ +		if (ap->userdata && xfs_inode_is_filestream(ap->ip)) +			error = xfs_bmap_btalloc_filestreams(ap, &args, &blen); +		else +			error = xfs_bmap_btalloc_nullfb(ap, &args, &blen);  		if (error)  			return error;  	} else if (ap->flist->xbf_low) { @@ -3728,7 +3777,7 @@ xfs_bmap_btalloc(  	 */  	if (!ap->flist->xbf_low && ap->aeof) {  		if (!ap->offset) { -			args.alignment = mp->m_dalign; +			args.alignment = stripe_align;  			atype = args.type;  			isaligned = 1;  			/* @@ -3753,13 +3802,13 @@ xfs_bmap_btalloc(  			 * of minlen+alignment+slop doesn't go up  			 * between the calls.  			 */ -			if (blen > mp->m_dalign && blen <= args.maxlen) -				nextminlen = blen - mp->m_dalign; +			if (blen > stripe_align && blen <= args.maxlen) +				nextminlen = blen - stripe_align;  			else  				nextminlen = args.minlen; -			if (nextminlen + mp->m_dalign > args.minlen + 1) +			if (nextminlen + stripe_align > args.minlen + 1)  				args.minalignslop = -					nextminlen + mp->m_dalign - +					nextminlen + stripe_align -  					args.minlen - 1;  			else  				args.minalignslop = 0; @@ -3781,7 +3830,7 @@ xfs_bmap_btalloc(  		 */  		args.type = atype;  		args.fsbno = ap->blkno; -		args.alignment = mp->m_dalign; +		args.alignment = stripe_align;  		args.minlen = nextminlen;  		args.minalignslop = 0;  		isaligned = 1; @@ -3995,6 +4044,7 @@ xfs_bmapi_read(  	ASSERT(*nmap >= 1);  	ASSERT(!(flags & ~(XFS_BMAPI_ATTRFORK|XFS_BMAPI_ENTIRE|  			   XFS_BMAPI_IGSTATE))); +	ASSERT(xfs_isilocked(ip, XFS_ILOCK_SHARED|XFS_ILOCK_EXCL));  	if (unlikely(XFS_TEST_ERROR(  	    (XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_EXTENTS && @@ -4189,6 +4239,7 @@ xfs_bmapi_delay(  	ASSERT(*nmap >= 1);  	ASSERT(*nmap <= XFS_BMAP_MAX_NMAP);  	ASSERT(!(flags & ~XFS_BMAPI_ENTIRE)); +	ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL));  	if (unlikely(XFS_TEST_ERROR(  	    (XFS_IFORK_FORMAT(ip, XFS_DATA_FORK) != XFS_DINODE_FMT_EXTENTS && @@ -4247,8 +4298,8 @@ xfs_bmapi_delay(  } -int -__xfs_bmapi_allocate( +static int +xfs_bmapi_allocate(  	struct xfs_bmalloca	*bma)  {  	struct xfs_mount	*mp = bma->ip->i_mount; @@ -4482,6 +4533,7 @@ xfs_bmapi_write(  	ASSERT(tp != NULL);  	ASSERT(len > 0);  	ASSERT(XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_LOCAL); +	ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL));  	if (unlikely(XFS_TEST_ERROR(  	    (XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_EXTENTS && @@ -4526,9 +4578,6 @@ xfs_bmapi_write(  	bma.flist = flist;  	bma.firstblock = firstblock; -	if (flags & XFS_BMAPI_STACK_SWITCH) -		bma.stack_switch = 1; -  	while (bno < end && n < *nmap) {  		inhole = eof || bma.got.br_startoff > bno;  		wasdelay = !inhole && isnullstartblock(bma.got.br_startblock); @@ -5033,6 +5082,7 @@ xfs_bunmapi(  	if (XFS_FORCED_SHUTDOWN(mp))  		return XFS_ERROR(EIO); +	ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL));  	ASSERT(len > 0);  	ASSERT(nexts >= 0); @@ -5356,3 +5406,201 @@ error0:  	}  	return error;  } + +/* + * Shift extent records to the left to cover a hole. + * + * The maximum number of extents to be shifted in a single operation + * is @num_exts, and @current_ext keeps track of the current extent + * index we have shifted. @offset_shift_fsb is the length by which each + * extent is shifted. If there is no hole to shift the extents + * into, this will be considered invalid operation and we abort immediately. + */ +int +xfs_bmap_shift_extents( +	struct xfs_trans	*tp, +	struct xfs_inode	*ip, +	int			*done, +	xfs_fileoff_t		start_fsb, +	xfs_fileoff_t		offset_shift_fsb, +	xfs_extnum_t		*current_ext, +	xfs_fsblock_t		*firstblock, +	struct xfs_bmap_free	*flist, +	int			num_exts) +{ +	struct xfs_btree_cur		*cur; +	struct xfs_bmbt_rec_host	*gotp; +	struct xfs_bmbt_irec            got; +	struct xfs_bmbt_irec		left; +	struct xfs_mount		*mp = ip->i_mount; +	struct xfs_ifork		*ifp; +	xfs_extnum_t			nexts = 0; +	xfs_fileoff_t			startoff; +	int				error = 0; +	int				i; +	int				whichfork = XFS_DATA_FORK; +	int				logflags; +	xfs_filblks_t			blockcount = 0; +	int				total_extents; + +	if (unlikely(XFS_TEST_ERROR( +	    (XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_EXTENTS && +	     XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_BTREE), +	     mp, XFS_ERRTAG_BMAPIFORMAT, XFS_RANDOM_BMAPIFORMAT))) { +		XFS_ERROR_REPORT("xfs_bmap_shift_extents", +				 XFS_ERRLEVEL_LOW, mp); +		return XFS_ERROR(EFSCORRUPTED); +	} + +	if (XFS_FORCED_SHUTDOWN(mp)) +		return XFS_ERROR(EIO); + +	ASSERT(current_ext != NULL); + +	ifp = XFS_IFORK_PTR(ip, whichfork); +	if (!(ifp->if_flags & XFS_IFEXTENTS)) { +		/* Read in all the extents */ +		error = xfs_iread_extents(tp, ip, whichfork); +		if (error) +			return error; +	} + +	/* +	 * If *current_ext is 0, we would need to lookup the extent +	 * from where we would start shifting and store it in gotp. +	 */ +	if (!*current_ext) { +		gotp = xfs_iext_bno_to_ext(ifp, start_fsb, current_ext); +		/* +		 * gotp can be null in 2 cases: 1) if there are no extents +		 * or 2) start_fsb lies in a hole beyond which there are +		 * no extents. Either way, we are done. +		 */ +		if (!gotp) { +			*done = 1; +			return 0; +		} +	} + +	/* We are going to change core inode */ +	logflags = XFS_ILOG_CORE; +	if (ifp->if_flags & XFS_IFBROOT) { +		cur = xfs_bmbt_init_cursor(mp, tp, ip, whichfork); +		cur->bc_private.b.firstblock = *firstblock; +		cur->bc_private.b.flist = flist; +		cur->bc_private.b.flags = 0; +	} else { +		cur = NULL; +		logflags |= XFS_ILOG_DEXT; +	} + +	/* +	 * There may be delalloc extents in the data fork before the range we +	 * are collapsing out, so we cannot +	 * use the count of real extents here. Instead we have to calculate it +	 * from the incore fork. +	 */ +	total_extents = ifp->if_bytes / sizeof(xfs_bmbt_rec_t); +	while (nexts++ < num_exts && *current_ext < total_extents) { + +		gotp = xfs_iext_get_ext(ifp, *current_ext); +		xfs_bmbt_get_all(gotp, &got); +		startoff = got.br_startoff - offset_shift_fsb; + +		/* +		 * Before shifting extent into hole, make sure that the hole +		 * is large enough to accomodate the shift. +		 */ +		if (*current_ext) { +			xfs_bmbt_get_all(xfs_iext_get_ext(ifp, +						*current_ext - 1), &left); + +			if (startoff < left.br_startoff + left.br_blockcount) +				error = XFS_ERROR(EINVAL); +		} else if (offset_shift_fsb > got.br_startoff) { +			/* +			 * When first extent is shifted, offset_shift_fsb +			 * should be less than the stating offset of +			 * the first extent. +			 */ +			error = XFS_ERROR(EINVAL); +		} + +		if (error) +			goto del_cursor; + +		if (cur) { +			error = xfs_bmbt_lookup_eq(cur, got.br_startoff, +						   got.br_startblock, +						   got.br_blockcount, +						   &i); +			if (error) +				goto del_cursor; +			XFS_WANT_CORRUPTED_GOTO(i == 1, del_cursor); +		} + +		/* Check if we can merge 2 adjacent extents */ +		if (*current_ext && +		    left.br_startoff + left.br_blockcount == startoff && +		    left.br_startblock + left.br_blockcount == +				got.br_startblock && +		    left.br_state == got.br_state && +		    left.br_blockcount + got.br_blockcount <= MAXEXTLEN) { +			blockcount = left.br_blockcount + +				got.br_blockcount; +			xfs_iext_remove(ip, *current_ext, 1, 0); +			if (cur) { +				error = xfs_btree_delete(cur, &i); +				if (error) +					goto del_cursor; +				XFS_WANT_CORRUPTED_GOTO(i == 1, del_cursor); +			} +			XFS_IFORK_NEXT_SET(ip, whichfork, +				XFS_IFORK_NEXTENTS(ip, whichfork) - 1); +			gotp = xfs_iext_get_ext(ifp, --*current_ext); +			xfs_bmbt_get_all(gotp, &got); + +			/* Make cursor point to the extent we will update */ +			if (cur) { +				error = xfs_bmbt_lookup_eq(cur, got.br_startoff, +							   got.br_startblock, +							   got.br_blockcount, +							   &i); +				if (error) +					goto del_cursor; +				XFS_WANT_CORRUPTED_GOTO(i == 1, del_cursor); +			} + +			xfs_bmbt_set_blockcount(gotp, blockcount); +			got.br_blockcount = blockcount; +		} else { +			/* We have to update the startoff */ +			xfs_bmbt_set_startoff(gotp, startoff); +			got.br_startoff = startoff; +		} + +		if (cur) { +			error = xfs_bmbt_update(cur, got.br_startoff, +						got.br_startblock, +						got.br_blockcount, +						got.br_state); +			if (error) +				goto del_cursor; +		} + +		(*current_ext)++; +		total_extents = ifp->if_bytes / sizeof(xfs_bmbt_rec_t); +	} + +	/* Check if we are done */ +	if (*current_ext == total_extents) +		*done = 1; + +del_cursor: +	if (cur) +		xfs_btree_del_cursor(cur, +			error ? XFS_BTREE_ERROR : XFS_BTREE_NOERROR); + +	xfs_trans_log_inode(tp, ip, logflags); +	return error; +}  | 
