diff options
Diffstat (limited to 'fs/xfs/libxfs')
| -rw-r--r-- | fs/xfs/libxfs/xfs_bmap.c | 34 | ||||
| -rw-r--r-- | fs/xfs/libxfs/xfs_bmap_btree.c | 6 | ||||
| -rw-r--r-- | fs/xfs/libxfs/xfs_dir2_priv.h | 1 | ||||
| -rw-r--r-- | fs/xfs/libxfs/xfs_dir2_sf.c | 106 | ||||
| -rw-r--r-- | fs/xfs/libxfs/xfs_inode_fork.c | 13 | 
5 files changed, 143 insertions, 17 deletions
diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c index a9c66d47757a..9bd104f32908 100644 --- a/fs/xfs/libxfs/xfs_bmap.c +++ b/fs/xfs/libxfs/xfs_bmap.c @@ -763,8 +763,8 @@ xfs_bmap_extents_to_btree(  		args.type = XFS_ALLOCTYPE_START_BNO;  		args.fsbno = XFS_INO_TO_FSB(mp, ip->i_ino);  	} else if (dfops->dop_low) { -try_another_ag:  		args.type = XFS_ALLOCTYPE_START_BNO; +try_another_ag:  		args.fsbno = *firstblock;  	} else {  		args.type = XFS_ALLOCTYPE_NEAR_BNO; @@ -790,13 +790,17 @@ try_another_ag:  	if (xfs_sb_version_hasreflink(&cur->bc_mp->m_sb) &&  	    args.fsbno == NULLFSBLOCK &&  	    args.type == XFS_ALLOCTYPE_NEAR_BNO) { -		dfops->dop_low = true; +		args.type = XFS_ALLOCTYPE_FIRST_AG;  		goto try_another_ag;  	} +	if (WARN_ON_ONCE(args.fsbno == NULLFSBLOCK)) { +		xfs_iroot_realloc(ip, -1, whichfork); +		xfs_btree_del_cursor(cur, XFS_BTREE_ERROR); +		return -ENOSPC; +	}  	/*  	 * Allocation can't fail, the space was reserved.  	 */ -	ASSERT(args.fsbno != NULLFSBLOCK);  	ASSERT(*firstblock == NULLFSBLOCK ||  	       args.agno >= XFS_FSB_TO_AGNO(mp, *firstblock));  	*firstblock = cur->bc_private.b.firstblock = args.fsbno; @@ -4150,6 +4154,19 @@ xfs_bmapi_read(  	return 0;  } +/* + * Add a delayed allocation extent to an inode. Blocks are reserved from the + * global pool and the extent inserted into the inode in-core extent tree. + * + * On entry, got refers to the first extent beyond the offset of the extent to + * allocate or eof is specified if no such extent exists. On return, got refers + * to the extent record that was inserted to the inode fork. + * + * Note that the allocated extent may have been merged with contiguous extents + * during insertion into the inode fork. Thus, got does not reflect the current + * state of the inode fork on return. If necessary, the caller can use lastx to + * look up the updated record in the inode fork. + */  int  xfs_bmapi_reserve_delalloc(  	struct xfs_inode	*ip, @@ -4236,13 +4253,8 @@ xfs_bmapi_reserve_delalloc(  	got->br_startblock = nullstartblock(indlen);  	got->br_blockcount = alen;  	got->br_state = XFS_EXT_NORM; -	xfs_bmap_add_extent_hole_delay(ip, whichfork, lastx, got); -	/* -	 * Update our extent pointer, given that xfs_bmap_add_extent_hole_delay -	 * might have merged it into one of the neighbouring ones. -	 */ -	xfs_bmbt_get_all(xfs_iext_get_ext(ifp, *lastx), got); +	xfs_bmap_add_extent_hole_delay(ip, whichfork, lastx, got);  	/*  	 * Tag the inode if blocks were preallocated. Note that COW fork @@ -4254,10 +4266,6 @@ xfs_bmapi_reserve_delalloc(  	if (whichfork == XFS_COW_FORK && (prealloc || aoff < off || alen > len))  		xfs_inode_set_cowblocks_tag(ip); -	ASSERT(got->br_startoff <= aoff); -	ASSERT(got->br_startoff + got->br_blockcount >= aoff + alen); -	ASSERT(isnullstartblock(got->br_startblock)); -	ASSERT(got->br_state == XFS_EXT_NORM);  	return 0;  out_unreserve_blocks: diff --git a/fs/xfs/libxfs/xfs_bmap_btree.c b/fs/xfs/libxfs/xfs_bmap_btree.c index f93072b58a58..fd55db479385 100644 --- a/fs/xfs/libxfs/xfs_bmap_btree.c +++ b/fs/xfs/libxfs/xfs_bmap_btree.c @@ -447,8 +447,8 @@ xfs_bmbt_alloc_block(  	if (args.fsbno == NULLFSBLOCK) {  		args.fsbno = be64_to_cpu(start->l); -try_another_ag:  		args.type = XFS_ALLOCTYPE_START_BNO; +try_another_ag:  		/*  		 * Make sure there is sufficient room left in the AG to  		 * complete a full tree split for an extent insert.  If @@ -488,8 +488,8 @@ try_another_ag:  	if (xfs_sb_version_hasreflink(&cur->bc_mp->m_sb) &&  	    args.fsbno == NULLFSBLOCK &&  	    args.type == XFS_ALLOCTYPE_NEAR_BNO) { -		cur->bc_private.b.dfops->dop_low = true;  		args.fsbno = cur->bc_private.b.firstblock; +		args.type = XFS_ALLOCTYPE_FIRST_AG;  		goto try_another_ag;  	} @@ -506,7 +506,7 @@ try_another_ag:  			goto error0;  		cur->bc_private.b.dfops->dop_low = true;  	} -	if (args.fsbno == NULLFSBLOCK) { +	if (WARN_ON_ONCE(args.fsbno == NULLFSBLOCK)) {  		XFS_BTREE_TRACE_CURSOR(cur, XBT_EXIT);  		*stat = 0;  		return 0; diff --git a/fs/xfs/libxfs/xfs_dir2_priv.h b/fs/xfs/libxfs/xfs_dir2_priv.h index d04547fcf274..39f8604f764e 100644 --- a/fs/xfs/libxfs/xfs_dir2_priv.h +++ b/fs/xfs/libxfs/xfs_dir2_priv.h @@ -125,6 +125,7 @@ extern int xfs_dir2_sf_create(struct xfs_da_args *args, xfs_ino_t pino);  extern int xfs_dir2_sf_lookup(struct xfs_da_args *args);  extern int xfs_dir2_sf_removename(struct xfs_da_args *args);  extern int xfs_dir2_sf_replace(struct xfs_da_args *args); +extern int xfs_dir2_sf_verify(struct xfs_inode *ip);  /* xfs_dir2_readdir.c */  extern int xfs_readdir(struct xfs_inode *dp, struct dir_context *ctx, diff --git a/fs/xfs/libxfs/xfs_dir2_sf.c b/fs/xfs/libxfs/xfs_dir2_sf.c index c6809ff41197..e84af093b2ab 100644 --- a/fs/xfs/libxfs/xfs_dir2_sf.c +++ b/fs/xfs/libxfs/xfs_dir2_sf.c @@ -629,6 +629,112 @@ xfs_dir2_sf_check(  }  #endif	/* DEBUG */ +/* Verify the consistency of an inline directory. */ +int +xfs_dir2_sf_verify( +	struct xfs_inode		*ip) +{ +	struct xfs_mount		*mp = ip->i_mount; +	struct xfs_dir2_sf_hdr		*sfp; +	struct xfs_dir2_sf_entry	*sfep; +	struct xfs_dir2_sf_entry	*next_sfep; +	char				*endp; +	const struct xfs_dir_ops	*dops; +	struct xfs_ifork		*ifp; +	xfs_ino_t			ino; +	int				i; +	int				i8count; +	int				offset; +	int				size; +	int				error; +	__uint8_t			filetype; + +	ASSERT(ip->i_d.di_format == XFS_DINODE_FMT_LOCAL); +	/* +	 * xfs_iread calls us before xfs_setup_inode sets up ip->d_ops, +	 * so we can only trust the mountpoint to have the right pointer. +	 */ +	dops = xfs_dir_get_ops(mp, NULL); + +	ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK); +	sfp = (struct xfs_dir2_sf_hdr *)ifp->if_u1.if_data; +	size = ifp->if_bytes; + +	/* +	 * Give up if the directory is way too short. +	 */ +	if (size <= offsetof(struct xfs_dir2_sf_hdr, parent) || +	    size < xfs_dir2_sf_hdr_size(sfp->i8count)) +		return -EFSCORRUPTED; + +	endp = (char *)sfp + size; + +	/* Check .. entry */ +	ino = dops->sf_get_parent_ino(sfp); +	i8count = ino > XFS_DIR2_MAX_SHORT_INUM; +	error = xfs_dir_ino_validate(mp, ino); +	if (error) +		return error; +	offset = dops->data_first_offset; + +	/* Check all reported entries */ +	sfep = xfs_dir2_sf_firstentry(sfp); +	for (i = 0; i < sfp->count; i++) { +		/* +		 * struct xfs_dir2_sf_entry has a variable length. +		 * Check the fixed-offset parts of the structure are +		 * within the data buffer. +		 */ +		if (((char *)sfep + sizeof(*sfep)) >= endp) +			return -EFSCORRUPTED; + +		/* Don't allow names with known bad length. */ +		if (sfep->namelen == 0) +			return -EFSCORRUPTED; + +		/* +		 * Check that the variable-length part of the structure is +		 * within the data buffer.  The next entry starts after the +		 * name component, so nextentry is an acceptable test. +		 */ +		next_sfep = dops->sf_nextentry(sfp, sfep); +		if (endp < (char *)next_sfep) +			return -EFSCORRUPTED; + +		/* Check that the offsets always increase. */ +		if (xfs_dir2_sf_get_offset(sfep) < offset) +			return -EFSCORRUPTED; + +		/* Check the inode number. */ +		ino = dops->sf_get_ino(sfp, sfep); +		i8count += ino > XFS_DIR2_MAX_SHORT_INUM; +		error = xfs_dir_ino_validate(mp, ino); +		if (error) +			return error; + +		/* Check the file type. */ +		filetype = dops->sf_get_ftype(sfep); +		if (filetype >= XFS_DIR3_FT_MAX) +			return -EFSCORRUPTED; + +		offset = xfs_dir2_sf_get_offset(sfep) + +				dops->data_entsize(sfep->namelen); + +		sfep = next_sfep; +	} +	if (i8count != sfp->i8count) +		return -EFSCORRUPTED; +	if ((void *)sfep != (void *)endp) +		return -EFSCORRUPTED; + +	/* Make sure this whole thing ought to be in local format. */ +	if (offset + (sfp->count + 2) * (uint)sizeof(xfs_dir2_leaf_entry_t) + +	    (uint)sizeof(xfs_dir2_block_tail_t) > mp->m_dir_geo->blksize) +		return -EFSCORRUPTED; + +	return 0; +} +  /*   * Create a new (shortform) directory.   */ diff --git a/fs/xfs/libxfs/xfs_inode_fork.c b/fs/xfs/libxfs/xfs_inode_fork.c index 25c1e078aef6..8a37efe04de3 100644 --- a/fs/xfs/libxfs/xfs_inode_fork.c +++ b/fs/xfs/libxfs/xfs_inode_fork.c @@ -33,6 +33,8 @@  #include "xfs_trace.h"  #include "xfs_attr_sf.h"  #include "xfs_da_format.h" +#include "xfs_da_btree.h" +#include "xfs_dir2_priv.h"  kmem_zone_t *xfs_ifork_zone; @@ -210,6 +212,16 @@ xfs_iformat_fork(  	if (error)  		return error; +	/* Check inline dir contents. */ +	if (S_ISDIR(VFS_I(ip)->i_mode) && +	    dip->di_format == XFS_DINODE_FMT_LOCAL) { +		error = xfs_dir2_sf_verify(ip); +		if (error) { +			xfs_idestroy_fork(ip, XFS_DATA_FORK); +			return error; +		} +	} +  	if (xfs_is_reflink_inode(ip)) {  		ASSERT(ip->i_cowfp == NULL);  		xfs_ifork_init_cow(ip); @@ -320,7 +332,6 @@ xfs_iformat_local(  	int		whichfork,  	int		size)  { -  	/*  	 * If the size is unreasonable, then something  	 * is wrong and we just bail out rather than crash in  | 
