2013-08-12 14:49:33 +04:00
/*
* Copyright ( c ) 2000 - 2006 Silicon Graphics , Inc .
* All Rights Reserved .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation .
*
* This program is distributed in the hope that it would be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write the Free Software Foundation ,
* Inc . , 51 Franklin St , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
# include <linux/log2.h>
# include "xfs.h"
# include "xfs_fs.h"
# include "xfs_format.h"
2013-10-23 03:50:10 +04:00
# include "xfs_log_format.h"
# include "xfs_trans_resv.h"
2013-08-12 14:49:33 +04:00
# include "xfs_inum.h"
# include "xfs_sb.h"
# include "xfs_ag.h"
# include "xfs_mount.h"
# include "xfs_inode.h"
2013-10-23 03:50:10 +04:00
# include "xfs_trans.h"
2013-08-12 14:49:33 +04:00
# include "xfs_inode_item.h"
2013-10-23 03:51:50 +04:00
# include "xfs_bmap_btree.h"
2013-08-12 14:49:33 +04:00
# include "xfs_bmap.h"
# include "xfs_error.h"
# include "xfs_trace.h"
2013-10-23 03:51:50 +04:00
# include "xfs_attr_sf.h"
# include "xfs_dinode.h"
2013-08-12 14:49:33 +04:00
kmem_zone_t * xfs_ifork_zone ;
STATIC int xfs_iformat_local ( xfs_inode_t * , xfs_dinode_t * , int , int ) ;
STATIC int xfs_iformat_extents ( xfs_inode_t * , xfs_dinode_t * , int ) ;
STATIC int xfs_iformat_btree ( xfs_inode_t * , xfs_dinode_t * , int ) ;
# ifdef DEBUG
/*
* Make sure that the extents in the given memory buffer
* are valid .
*/
void
xfs_validate_extents (
xfs_ifork_t * ifp ,
int nrecs ,
xfs_exntfmt_t fmt )
{
xfs_bmbt_irec_t irec ;
xfs_bmbt_rec_host_t rec ;
int i ;
for ( i = 0 ; i < nrecs ; i + + ) {
xfs_bmbt_rec_host_t * ep = xfs_iext_get_ext ( ifp , i ) ;
rec . l0 = get_unaligned ( & ep - > l0 ) ;
rec . l1 = get_unaligned ( & ep - > l1 ) ;
xfs_bmbt_get_all ( & rec , & irec ) ;
if ( fmt = = XFS_EXTFMT_NOSTATE )
ASSERT ( irec . br_state = = XFS_EXT_NORM ) ;
}
}
# else /* DEBUG */
# define xfs_validate_extents(ifp, nrecs, fmt)
# endif /* DEBUG */
/*
* Move inode type and inode format specific information from the
* on - disk inode to the in - core inode . For fifos , devs , and sockets
* this means set if_rdev to the proper value . For files , directories ,
* and symlinks this means to bring in the in - line data or extent
* pointers . For a file in B - tree format , only the root is immediately
* brought in - core . The rest will be in - lined in if_extents when it
* is first referenced ( see xfs_iread_extents ( ) ) .
*/
int
xfs_iformat_fork (
xfs_inode_t * ip ,
xfs_dinode_t * dip )
{
xfs_attr_shortform_t * atp ;
int size ;
int error = 0 ;
xfs_fsize_t di_size ;
if ( unlikely ( be32_to_cpu ( dip - > di_nextents ) +
be16_to_cpu ( dip - > di_anextents ) >
be64_to_cpu ( dip - > di_nblocks ) ) ) {
xfs_warn ( ip - > i_mount ,
" corrupt dinode %Lu, extent total = %d, nblocks = %Lu. " ,
( unsigned long long ) ip - > i_ino ,
( int ) ( be32_to_cpu ( dip - > di_nextents ) +
be16_to_cpu ( dip - > di_anextents ) ) ,
( unsigned long long )
be64_to_cpu ( dip - > di_nblocks ) ) ;
XFS_CORRUPTION_ERROR ( " xfs_iformat(1) " , XFS_ERRLEVEL_LOW ,
ip - > i_mount , dip ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
if ( unlikely ( dip - > di_forkoff > ip - > i_mount - > m_sb . sb_inodesize ) ) {
xfs_warn ( ip - > i_mount , " corrupt dinode %Lu, forkoff = 0x%x. " ,
( unsigned long long ) ip - > i_ino ,
dip - > di_forkoff ) ;
XFS_CORRUPTION_ERROR ( " xfs_iformat(2) " , XFS_ERRLEVEL_LOW ,
ip - > i_mount , dip ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
if ( unlikely ( ( ip - > i_d . di_flags & XFS_DIFLAG_REALTIME ) & &
! ip - > i_mount - > m_rtdev_targp ) ) {
xfs_warn ( ip - > i_mount ,
" corrupt dinode %Lu, has realtime flag set. " ,
ip - > i_ino ) ;
XFS_CORRUPTION_ERROR ( " xfs_iformat(realtime) " ,
XFS_ERRLEVEL_LOW , ip - > i_mount , dip ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
switch ( ip - > i_d . di_mode & S_IFMT ) {
case S_IFIFO :
case S_IFCHR :
case S_IFBLK :
case S_IFSOCK :
if ( unlikely ( dip - > di_format ! = XFS_DINODE_FMT_DEV ) ) {
XFS_CORRUPTION_ERROR ( " xfs_iformat(3) " , XFS_ERRLEVEL_LOW ,
ip - > i_mount , dip ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
ip - > i_d . di_size = 0 ;
ip - > i_df . if_u2 . if_rdev = xfs_dinode_get_rdev ( dip ) ;
break ;
case S_IFREG :
case S_IFLNK :
case S_IFDIR :
switch ( dip - > di_format ) {
case XFS_DINODE_FMT_LOCAL :
/*
* no local regular files yet
*/
if ( unlikely ( S_ISREG ( be16_to_cpu ( dip - > di_mode ) ) ) ) {
xfs_warn ( ip - > i_mount ,
" corrupt inode %Lu (local format for regular file). " ,
( unsigned long long ) ip - > i_ino ) ;
XFS_CORRUPTION_ERROR ( " xfs_iformat(4) " ,
XFS_ERRLEVEL_LOW ,
ip - > i_mount , dip ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
di_size = be64_to_cpu ( dip - > di_size ) ;
2013-08-15 09:53:38 +04:00
if ( unlikely ( di_size < 0 | |
di_size > XFS_DFORK_DSIZE ( dip , ip - > i_mount ) ) ) {
2013-08-12 14:49:33 +04:00
xfs_warn ( ip - > i_mount ,
" corrupt inode %Lu (bad size %Ld for local inode). " ,
( unsigned long long ) ip - > i_ino ,
( long long ) di_size ) ;
XFS_CORRUPTION_ERROR ( " xfs_iformat(5) " ,
XFS_ERRLEVEL_LOW ,
ip - > i_mount , dip ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
size = ( int ) di_size ;
error = xfs_iformat_local ( ip , dip , XFS_DATA_FORK , size ) ;
break ;
case XFS_DINODE_FMT_EXTENTS :
error = xfs_iformat_extents ( ip , dip , XFS_DATA_FORK ) ;
break ;
case XFS_DINODE_FMT_BTREE :
error = xfs_iformat_btree ( ip , dip , XFS_DATA_FORK ) ;
break ;
default :
XFS_ERROR_REPORT ( " xfs_iformat(6) " , XFS_ERRLEVEL_LOW ,
ip - > i_mount ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
break ;
default :
XFS_ERROR_REPORT ( " xfs_iformat(7) " , XFS_ERRLEVEL_LOW , ip - > i_mount ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
if ( error ) {
return error ;
}
if ( ! XFS_DFORK_Q ( dip ) )
return 0 ;
ASSERT ( ip - > i_afp = = NULL ) ;
ip - > i_afp = kmem_zone_zalloc ( xfs_ifork_zone , KM_SLEEP | KM_NOFS ) ;
switch ( dip - > di_aformat ) {
case XFS_DINODE_FMT_LOCAL :
atp = ( xfs_attr_shortform_t * ) XFS_DFORK_APTR ( dip ) ;
size = be16_to_cpu ( atp - > hdr . totsize ) ;
if ( unlikely ( size < sizeof ( struct xfs_attr_sf_hdr ) ) ) {
xfs_warn ( ip - > i_mount ,
" corrupt inode %Lu (bad attr fork size %Ld). " ,
( unsigned long long ) ip - > i_ino ,
( long long ) size ) ;
XFS_CORRUPTION_ERROR ( " xfs_iformat(8) " ,
XFS_ERRLEVEL_LOW ,
ip - > i_mount , dip ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
error = xfs_iformat_local ( ip , dip , XFS_ATTR_FORK , size ) ;
break ;
case XFS_DINODE_FMT_EXTENTS :
error = xfs_iformat_extents ( ip , dip , XFS_ATTR_FORK ) ;
break ;
case XFS_DINODE_FMT_BTREE :
error = xfs_iformat_btree ( ip , dip , XFS_ATTR_FORK ) ;
break ;
default :
error = XFS_ERROR ( EFSCORRUPTED ) ;
break ;
}
if ( error ) {
kmem_zone_free ( xfs_ifork_zone , ip - > i_afp ) ;
ip - > i_afp = NULL ;
xfs_idestroy_fork ( ip , XFS_DATA_FORK ) ;
}
return error ;
}
/*
* The file is in - lined in the on - disk inode .
* If it fits into if_inline_data , then copy
* it there , otherwise allocate a buffer for it
* and copy the data there . Either way , set
* if_data to point at the data .
* If we allocate a buffer for the data , make
* sure that its size is a multiple of 4 and
* record the real size in i_real_bytes .
*/
STATIC int
xfs_iformat_local (
xfs_inode_t * ip ,
xfs_dinode_t * dip ,
int whichfork ,
int size )
{
xfs_ifork_t * ifp ;
int real_size ;
/*
* If the size is unreasonable , then something
* is wrong and we just bail out rather than crash in
* kmem_alloc ( ) or memcpy ( ) below .
*/
if ( unlikely ( size > XFS_DFORK_SIZE ( dip , ip - > i_mount , whichfork ) ) ) {
xfs_warn ( ip - > i_mount ,
" corrupt inode %Lu (bad size %d for local fork, size = %d). " ,
( unsigned long long ) ip - > i_ino , size ,
XFS_DFORK_SIZE ( dip , ip - > i_mount , whichfork ) ) ;
XFS_CORRUPTION_ERROR ( " xfs_iformat_local " , XFS_ERRLEVEL_LOW ,
ip - > i_mount , dip ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
ifp = XFS_IFORK_PTR ( ip , whichfork ) ;
real_size = 0 ;
if ( size = = 0 )
ifp - > if_u1 . if_data = NULL ;
else if ( size < = sizeof ( ifp - > if_u2 . if_inline_data ) )
ifp - > if_u1 . if_data = ifp - > if_u2 . if_inline_data ;
else {
real_size = roundup ( size , 4 ) ;
ifp - > if_u1 . if_data = kmem_alloc ( real_size , KM_SLEEP | KM_NOFS ) ;
}
ifp - > if_bytes = size ;
ifp - > if_real_bytes = real_size ;
if ( size )
memcpy ( ifp - > if_u1 . if_data , XFS_DFORK_PTR ( dip , whichfork ) , size ) ;
ifp - > if_flags & = ~ XFS_IFEXTENTS ;
ifp - > if_flags | = XFS_IFINLINE ;
return 0 ;
}
/*
* The file consists of a set of extents all
* of which fit into the on - disk inode .
* If there are few enough extents to fit into
* the if_inline_ext , then copy them there .
* Otherwise allocate a buffer for them and copy
* them into it . Either way , set if_extents
* to point at the extents .
*/
STATIC int
xfs_iformat_extents (
xfs_inode_t * ip ,
xfs_dinode_t * dip ,
int whichfork )
{
xfs_bmbt_rec_t * dp ;
xfs_ifork_t * ifp ;
int nex ;
int size ;
int i ;
ifp = XFS_IFORK_PTR ( ip , whichfork ) ;
nex = XFS_DFORK_NEXTENTS ( dip , whichfork ) ;
size = nex * ( uint ) sizeof ( xfs_bmbt_rec_t ) ;
/*
* If the number of extents is unreasonable , then something
* is wrong and we just bail out rather than crash in
* kmem_alloc ( ) or memcpy ( ) below .
*/
if ( unlikely ( size < 0 | | size > XFS_DFORK_SIZE ( dip , ip - > i_mount , whichfork ) ) ) {
xfs_warn ( ip - > i_mount , " corrupt inode %Lu ((a)extents = %d). " ,
( unsigned long long ) ip - > i_ino , nex ) ;
XFS_CORRUPTION_ERROR ( " xfs_iformat_extents(1) " , XFS_ERRLEVEL_LOW ,
ip - > i_mount , dip ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
ifp - > if_real_bytes = 0 ;
if ( nex = = 0 )
ifp - > if_u1 . if_extents = NULL ;
else if ( nex < = XFS_INLINE_EXTS )
ifp - > if_u1 . if_extents = ifp - > if_u2 . if_inline_ext ;
else
xfs_iext_add ( ifp , 0 , nex ) ;
ifp - > if_bytes = size ;
if ( size ) {
dp = ( xfs_bmbt_rec_t * ) XFS_DFORK_PTR ( dip , whichfork ) ;
xfs_validate_extents ( ifp , nex , XFS_EXTFMT_INODE ( ip ) ) ;
for ( i = 0 ; i < nex ; i + + , dp + + ) {
xfs_bmbt_rec_host_t * ep = xfs_iext_get_ext ( ifp , i ) ;
ep - > l0 = get_unaligned_be64 ( & dp - > l0 ) ;
ep - > l1 = get_unaligned_be64 ( & dp - > l1 ) ;
}
XFS_BMAP_TRACE_EXLIST ( ip , nex , whichfork ) ;
if ( whichfork ! = XFS_DATA_FORK | |
XFS_EXTFMT_INODE ( ip ) = = XFS_EXTFMT_NOSTATE )
if ( unlikely ( xfs_check_nostate_extents (
ifp , 0 , nex ) ) ) {
XFS_ERROR_REPORT ( " xfs_iformat_extents(2) " ,
XFS_ERRLEVEL_LOW ,
ip - > i_mount ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
}
ifp - > if_flags | = XFS_IFEXTENTS ;
return 0 ;
}
/*
* The file has too many extents to fit into
* the inode , so they are in B - tree format .
* Allocate a buffer for the root of the B - tree
* and copy the root into it . The i_extents
* field will remain NULL until all of the
* extents are read in ( when they are needed ) .
*/
STATIC int
xfs_iformat_btree (
xfs_inode_t * ip ,
xfs_dinode_t * dip ,
int whichfork )
{
struct xfs_mount * mp = ip - > i_mount ;
xfs_bmdr_block_t * dfp ;
xfs_ifork_t * ifp ;
/* REFERENCED */
int nrecs ;
int size ;
ifp = XFS_IFORK_PTR ( ip , whichfork ) ;
dfp = ( xfs_bmdr_block_t * ) XFS_DFORK_PTR ( dip , whichfork ) ;
size = XFS_BMAP_BROOT_SPACE ( mp , dfp ) ;
nrecs = be16_to_cpu ( dfp - > bb_numrecs ) ;
/*
* blow out if - - fork has less extents than can fit in
* fork ( fork shouldn ' t be a btree format ) , root btree
* block has more records than can fit into the fork ,
* or the number of extents is greater than the number of
* blocks .
*/
if ( unlikely ( XFS_IFORK_NEXTENTS ( ip , whichfork ) < =
XFS_IFORK_MAXEXT ( ip , whichfork ) | |
XFS_BMDR_SPACE_CALC ( nrecs ) >
XFS_DFORK_SIZE ( dip , mp , whichfork ) | |
XFS_IFORK_NEXTENTS ( ip , whichfork ) > ip - > i_d . di_nblocks ) ) {
xfs_warn ( mp , " corrupt inode %Lu (btree). " ,
( unsigned long long ) ip - > i_ino ) ;
XFS_CORRUPTION_ERROR ( " xfs_iformat_btree " , XFS_ERRLEVEL_LOW ,
mp , dip ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
ifp - > if_broot_bytes = size ;
ifp - > if_broot = kmem_alloc ( size , KM_SLEEP | KM_NOFS ) ;
ASSERT ( ifp - > if_broot ! = NULL ) ;
/*
* Copy and convert from the on - disk structure
* to the in - memory structure .
*/
xfs_bmdr_to_bmbt ( ip , dfp , XFS_DFORK_SIZE ( dip , ip - > i_mount , whichfork ) ,
ifp - > if_broot , size ) ;
ifp - > if_flags & = ~ XFS_IFEXTENTS ;
ifp - > if_flags | = XFS_IFBROOT ;
return 0 ;
}
/*
* Read in extents from a btree - format inode .
* Allocate and fill in if_extents . Real work is done in xfs_bmap . c .
*/
int
xfs_iread_extents (
xfs_trans_t * tp ,
xfs_inode_t * ip ,
int whichfork )
{
int error ;
xfs_ifork_t * ifp ;
xfs_extnum_t nextents ;
2013-12-07 00:30:17 +04:00
ASSERT ( xfs_isilocked ( ip , XFS_ILOCK_EXCL ) ) ;
2013-08-12 14:49:33 +04:00
if ( unlikely ( XFS_IFORK_FORMAT ( ip , whichfork ) ! = XFS_DINODE_FMT_BTREE ) ) {
XFS_ERROR_REPORT ( " xfs_iread_extents " , XFS_ERRLEVEL_LOW ,
ip - > i_mount ) ;
return XFS_ERROR ( EFSCORRUPTED ) ;
}
nextents = XFS_IFORK_NEXTENTS ( ip , whichfork ) ;
ifp = XFS_IFORK_PTR ( ip , whichfork ) ;
/*
* We know that the size is valid ( it ' s checked in iformat_btree )
*/
ifp - > if_bytes = ifp - > if_real_bytes = 0 ;
ifp - > if_flags | = XFS_IFEXTENTS ;
xfs_iext_add ( ifp , 0 , nextents ) ;
error = xfs_bmap_read_extents ( tp , ip , whichfork ) ;
if ( error ) {
xfs_iext_destroy ( ifp ) ;
ifp - > if_flags & = ~ XFS_IFEXTENTS ;
return error ;
}
xfs_validate_extents ( ifp , nextents , XFS_EXTFMT_INODE ( ip ) ) ;
return 0 ;
}
/*
* Reallocate the space for if_broot based on the number of records
* being added or deleted as indicated in rec_diff . Move the records
* and pointers in if_broot to fit the new size . When shrinking this
* will eliminate holes between the records and pointers created by
* the caller . When growing this will create holes to be filled in
* by the caller .
*
* The caller must not request to add more records than would fit in
* the on - disk inode root . If the if_broot is currently NULL , then
2013-08-07 14:11:04 +04:00
* if we are adding records , one will be allocated . The caller must also
2013-08-12 14:49:33 +04:00
* not request that the number of records go below zero , although
* it can go to zero .
*
* ip - - the inode whose if_broot area is changing
* ext_diff - - the change in the number of records , positive or negative ,
* requested for the if_broot array .
*/
void
xfs_iroot_realloc (
xfs_inode_t * ip ,
int rec_diff ,
int whichfork )
{
struct xfs_mount * mp = ip - > i_mount ;
int cur_max ;
xfs_ifork_t * ifp ;
struct xfs_btree_block * new_broot ;
int new_max ;
size_t new_size ;
char * np ;
char * op ;
/*
* Handle the degenerate case quietly .
*/
if ( rec_diff = = 0 ) {
return ;
}
ifp = XFS_IFORK_PTR ( ip , whichfork ) ;
if ( rec_diff > 0 ) {
/*
* If there wasn ' t any memory allocated before , just
* allocate it now and get out .
*/
if ( ifp - > if_broot_bytes = = 0 ) {
new_size = XFS_BMAP_BROOT_SPACE_CALC ( mp , rec_diff ) ;
ifp - > if_broot = kmem_alloc ( new_size , KM_SLEEP | KM_NOFS ) ;
ifp - > if_broot_bytes = ( int ) new_size ;
return ;
}
/*
* If there is already an existing if_broot , then we need
* to realloc ( ) it and shift the pointers to their new
* location . The records don ' t change location because
* they are kept butted up against the btree block header .
*/
cur_max = xfs_bmbt_maxrecs ( mp , ifp - > if_broot_bytes , 0 ) ;
new_max = cur_max + rec_diff ;
new_size = XFS_BMAP_BROOT_SPACE_CALC ( mp , new_max ) ;
ifp - > if_broot = kmem_realloc ( ifp - > if_broot , new_size ,
XFS_BMAP_BROOT_SPACE_CALC ( mp , cur_max ) ,
KM_SLEEP | KM_NOFS ) ;
op = ( char * ) XFS_BMAP_BROOT_PTR_ADDR ( mp , ifp - > if_broot , 1 ,
ifp - > if_broot_bytes ) ;
np = ( char * ) XFS_BMAP_BROOT_PTR_ADDR ( mp , ifp - > if_broot , 1 ,
( int ) new_size ) ;
ifp - > if_broot_bytes = ( int ) new_size ;
ASSERT ( XFS_BMAP_BMDR_SPACE ( ifp - > if_broot ) < =
XFS_IFORK_SIZE ( ip , whichfork ) ) ;
memmove ( np , op , cur_max * ( uint ) sizeof ( xfs_dfsbno_t ) ) ;
return ;
}
/*
* rec_diff is less than 0. In this case , we are shrinking the
* if_broot buffer . It must already exist . If we go to zero
* records , just get rid of the root and clear the status bit .
*/
ASSERT ( ( ifp - > if_broot ! = NULL ) & & ( ifp - > if_broot_bytes > 0 ) ) ;
cur_max = xfs_bmbt_maxrecs ( mp , ifp - > if_broot_bytes , 0 ) ;
new_max = cur_max + rec_diff ;
ASSERT ( new_max > = 0 ) ;
if ( new_max > 0 )
new_size = XFS_BMAP_BROOT_SPACE_CALC ( mp , new_max ) ;
else
new_size = 0 ;
if ( new_size > 0 ) {
new_broot = kmem_alloc ( new_size , KM_SLEEP | KM_NOFS ) ;
/*
* First copy over the btree block header .
*/
memcpy ( new_broot , ifp - > if_broot ,
XFS_BMBT_BLOCK_LEN ( ip - > i_mount ) ) ;
} else {
new_broot = NULL ;
ifp - > if_flags & = ~ XFS_IFBROOT ;
}
/*
* Only copy the records and pointers if there are any .
*/
if ( new_max > 0 ) {
/*
* First copy the records .
*/
op = ( char * ) XFS_BMBT_REC_ADDR ( mp , ifp - > if_broot , 1 ) ;
np = ( char * ) XFS_BMBT_REC_ADDR ( mp , new_broot , 1 ) ;
memcpy ( np , op , new_max * ( uint ) sizeof ( xfs_bmbt_rec_t ) ) ;
/*
* Then copy the pointers .
*/
op = ( char * ) XFS_BMAP_BROOT_PTR_ADDR ( mp , ifp - > if_broot , 1 ,
ifp - > if_broot_bytes ) ;
np = ( char * ) XFS_BMAP_BROOT_PTR_ADDR ( mp , new_broot , 1 ,
( int ) new_size ) ;
memcpy ( np , op , new_max * ( uint ) sizeof ( xfs_dfsbno_t ) ) ;
}
kmem_free ( ifp - > if_broot ) ;
ifp - > if_broot = new_broot ;
ifp - > if_broot_bytes = ( int ) new_size ;
if ( ifp - > if_broot )
ASSERT ( XFS_BMAP_BMDR_SPACE ( ifp - > if_broot ) < =
XFS_IFORK_SIZE ( ip , whichfork ) ) ;
return ;
}
/*
* This is called when the amount of space needed for if_data
* is increased or decreased . The change in size is indicated by
* the number of bytes that need to be added or deleted in the
* byte_diff parameter .
*
* If the amount of space needed has decreased below the size of the
* inline buffer , then switch to using the inline buffer . Otherwise ,
* use kmem_realloc ( ) or kmem_alloc ( ) to adjust the size of the buffer
* to what is needed .
*
* ip - - the inode whose if_data area is changing
* byte_diff - - the change in the number of bytes , positive or negative ,
* requested for the if_data array .
*/
void
xfs_idata_realloc (
xfs_inode_t * ip ,
int byte_diff ,
int whichfork )
{
xfs_ifork_t * ifp ;
int new_size ;
int real_size ;
if ( byte_diff = = 0 ) {
return ;
}
ifp = XFS_IFORK_PTR ( ip , whichfork ) ;
new_size = ( int ) ifp - > if_bytes + byte_diff ;
ASSERT ( new_size > = 0 ) ;
if ( new_size = = 0 ) {
if ( ifp - > if_u1 . if_data ! = ifp - > if_u2 . if_inline_data ) {
kmem_free ( ifp - > if_u1 . if_data ) ;
}
ifp - > if_u1 . if_data = NULL ;
real_size = 0 ;
} else if ( new_size < = sizeof ( ifp - > if_u2 . if_inline_data ) ) {
/*
* If the valid extents / data can fit in if_inline_ext / data ,
* copy them from the malloc ' d vector and free it .
*/
if ( ifp - > if_u1 . if_data = = NULL ) {
ifp - > if_u1 . if_data = ifp - > if_u2 . if_inline_data ;
} else if ( ifp - > if_u1 . if_data ! = ifp - > if_u2 . if_inline_data ) {
ASSERT ( ifp - > if_real_bytes ! = 0 ) ;
memcpy ( ifp - > if_u2 . if_inline_data , ifp - > if_u1 . if_data ,
new_size ) ;
kmem_free ( ifp - > if_u1 . if_data ) ;
ifp - > if_u1 . if_data = ifp - > if_u2 . if_inline_data ;
}
real_size = 0 ;
} else {
/*
* Stuck with malloc / realloc .
* For inline data , the underlying buffer must be
* a multiple of 4 bytes in size so that it can be
* logged and stay on word boundaries . We enforce
* that here .
*/
real_size = roundup ( new_size , 4 ) ;
if ( ifp - > if_u1 . if_data = = NULL ) {
ASSERT ( ifp - > if_real_bytes = = 0 ) ;
ifp - > if_u1 . if_data = kmem_alloc ( real_size ,
KM_SLEEP | KM_NOFS ) ;
} else if ( ifp - > if_u1 . if_data ! = ifp - > if_u2 . if_inline_data ) {
/*
* Only do the realloc if the underlying size
* is really changing .
*/
if ( ifp - > if_real_bytes ! = real_size ) {
ifp - > if_u1 . if_data =
kmem_realloc ( ifp - > if_u1 . if_data ,
real_size ,
ifp - > if_real_bytes ,
KM_SLEEP | KM_NOFS ) ;
}
} else {
ASSERT ( ifp - > if_real_bytes = = 0 ) ;
ifp - > if_u1 . if_data = kmem_alloc ( real_size ,
KM_SLEEP | KM_NOFS ) ;
memcpy ( ifp - > if_u1 . if_data , ifp - > if_u2 . if_inline_data ,
ifp - > if_bytes ) ;
}
}
ifp - > if_real_bytes = real_size ;
ifp - > if_bytes = new_size ;
ASSERT ( ifp - > if_bytes < = XFS_IFORK_SIZE ( ip , whichfork ) ) ;
}
void
xfs_idestroy_fork (
xfs_inode_t * ip ,
int whichfork )
{
xfs_ifork_t * ifp ;
ifp = XFS_IFORK_PTR ( ip , whichfork ) ;
if ( ifp - > if_broot ! = NULL ) {
kmem_free ( ifp - > if_broot ) ;
ifp - > if_broot = NULL ;
}
/*
* If the format is local , then we can ' t have an extents
* array so just look for an inline data array . If we ' re
* not local then we may or may not have an extents list ,
* so check and free it up if we do .
*/
if ( XFS_IFORK_FORMAT ( ip , whichfork ) = = XFS_DINODE_FMT_LOCAL ) {
if ( ( ifp - > if_u1 . if_data ! = ifp - > if_u2 . if_inline_data ) & &
( ifp - > if_u1 . if_data ! = NULL ) ) {
ASSERT ( ifp - > if_real_bytes ! = 0 ) ;
kmem_free ( ifp - > if_u1 . if_data ) ;
ifp - > if_u1 . if_data = NULL ;
ifp - > if_real_bytes = 0 ;
}
} else if ( ( ifp - > if_flags & XFS_IFEXTENTS ) & &
( ( ifp - > if_flags & XFS_IFEXTIREC ) | |
( ( ifp - > if_u1 . if_extents ! = NULL ) & &
( ifp - > if_u1 . if_extents ! = ifp - > if_u2 . if_inline_ext ) ) ) ) {
ASSERT ( ifp - > if_real_bytes ! = 0 ) ;
xfs_iext_destroy ( ifp ) ;
}
ASSERT ( ifp - > if_u1 . if_extents = = NULL | |
ifp - > if_u1 . if_extents = = ifp - > if_u2 . if_inline_ext ) ;
ASSERT ( ifp - > if_real_bytes = = 0 ) ;
if ( whichfork = = XFS_ATTR_FORK ) {
kmem_zone_free ( xfs_ifork_zone , ip - > i_afp ) ;
ip - > i_afp = NULL ;
}
}
/*
2013-12-13 04:34:04 +04:00
* Convert in - core extents to on - disk form
2013-08-12 14:49:33 +04:00
*
2013-12-13 04:34:04 +04:00
* For either the data or attr fork in extent format , we need to endian convert
* the in - core extent as we place them into the on - disk inode .
2013-08-12 14:49:33 +04:00
*
2013-12-13 04:34:04 +04:00
* In the case of the data fork , the in - core and on - disk fork sizes can be
* different due to delayed allocation extents . We only copy on - disk extents
* here , so callers must always use the physical fork size to determine the
* size of the buffer passed to this routine . We will return the size actually
* used .
2013-08-12 14:49:33 +04:00
*/
int
xfs_iextents_copy (
xfs_inode_t * ip ,
xfs_bmbt_rec_t * dp ,
int whichfork )
{
int copied ;
int i ;
xfs_ifork_t * ifp ;
int nrecs ;
xfs_fsblock_t start_block ;
ifp = XFS_IFORK_PTR ( ip , whichfork ) ;
ASSERT ( xfs_isilocked ( ip , XFS_ILOCK_EXCL | XFS_ILOCK_SHARED ) ) ;
ASSERT ( ifp - > if_bytes > 0 ) ;
nrecs = ifp - > if_bytes / ( uint ) sizeof ( xfs_bmbt_rec_t ) ;
XFS_BMAP_TRACE_EXLIST ( ip , nrecs , whichfork ) ;
ASSERT ( nrecs > 0 ) ;
/*
* There are some delayed allocation extents in the
* inode , so copy the extents one at a time and skip
* the delayed ones . There must be at least one
* non - delayed extent .
*/
copied = 0 ;
for ( i = 0 ; i < nrecs ; i + + ) {
xfs_bmbt_rec_host_t * ep = xfs_iext_get_ext ( ifp , i ) ;
start_block = xfs_bmbt_get_startblock ( ep ) ;
if ( isnullstartblock ( start_block ) ) {
/*
* It ' s a delayed allocation extent , so skip it .
*/
continue ;
}
/* Translate to on disk format */
2013-08-12 14:49:43 +04:00
put_unaligned_be64 ( ep - > l0 , & dp - > l0 ) ;
put_unaligned_be64 ( ep - > l1 , & dp - > l1 ) ;
2013-08-12 14:49:33 +04:00
dp + + ;
copied + + ;
}
ASSERT ( copied ! = 0 ) ;
xfs_validate_extents ( ifp , copied , XFS_EXTFMT_INODE ( ip ) ) ;
return ( copied * ( uint ) sizeof ( xfs_bmbt_rec_t ) ) ;
}
/*
* Each of the following cases stores data into the same region
* of the on - disk inode , so only one of them can be valid at
* any given time . While it is possible to have conflicting formats
* and log flags , e . g . having XFS_ILOG_ ? DATA set when the fork is
* in EXTENTS format , this can only happen when the fork has
* changed formats after being modified but before being flushed .
* In these cases , the format always takes precedence , because the
* format indicates the current state of the fork .
*/
void
xfs_iflush_fork (
xfs_inode_t * ip ,
xfs_dinode_t * dip ,
xfs_inode_log_item_t * iip ,
int whichfork ,
xfs_buf_t * bp )
{
char * cp ;
xfs_ifork_t * ifp ;
xfs_mount_t * mp ;
static const short brootflag [ 2 ] =
{ XFS_ILOG_DBROOT , XFS_ILOG_ABROOT } ;
static const short dataflag [ 2 ] =
{ XFS_ILOG_DDATA , XFS_ILOG_ADATA } ;
static const short extflag [ 2 ] =
{ XFS_ILOG_DEXT , XFS_ILOG_AEXT } ;
if ( ! iip )
return ;
ifp = XFS_IFORK_PTR ( ip , whichfork ) ;
/*
* This can happen if we gave up in iformat in an error path ,
* for the attribute fork .
*/
if ( ! ifp ) {
ASSERT ( whichfork = = XFS_ATTR_FORK ) ;
return ;
}
cp = XFS_DFORK_PTR ( dip , whichfork ) ;
mp = ip - > i_mount ;
switch ( XFS_IFORK_FORMAT ( ip , whichfork ) ) {
case XFS_DINODE_FMT_LOCAL :
if ( ( iip - > ili_fields & dataflag [ whichfork ] ) & &
( ifp - > if_bytes > 0 ) ) {
ASSERT ( ifp - > if_u1 . if_data ! = NULL ) ;
ASSERT ( ifp - > if_bytes < = XFS_IFORK_SIZE ( ip , whichfork ) ) ;
memcpy ( cp , ifp - > if_u1 . if_data , ifp - > if_bytes ) ;
}
break ;
case XFS_DINODE_FMT_EXTENTS :
ASSERT ( ( ifp - > if_flags & XFS_IFEXTENTS ) | |
! ( iip - > ili_fields & extflag [ whichfork ] ) ) ;
if ( ( iip - > ili_fields & extflag [ whichfork ] ) & &
( ifp - > if_bytes > 0 ) ) {
ASSERT ( xfs_iext_get_ext ( ifp , 0 ) ) ;
ASSERT ( XFS_IFORK_NEXTENTS ( ip , whichfork ) > 0 ) ;
( void ) xfs_iextents_copy ( ip , ( xfs_bmbt_rec_t * ) cp ,
whichfork ) ;
}
break ;
case XFS_DINODE_FMT_BTREE :
if ( ( iip - > ili_fields & brootflag [ whichfork ] ) & &
( ifp - > if_broot_bytes > 0 ) ) {
ASSERT ( ifp - > if_broot ! = NULL ) ;
ASSERT ( XFS_BMAP_BMDR_SPACE ( ifp - > if_broot ) < =
XFS_IFORK_SIZE ( ip , whichfork ) ) ;
xfs_bmbt_to_bmdr ( mp , ifp - > if_broot , ifp - > if_broot_bytes ,
( xfs_bmdr_block_t * ) cp ,
XFS_DFORK_SIZE ( dip , mp , whichfork ) ) ;
}
break ;
case XFS_DINODE_FMT_DEV :
if ( iip - > ili_fields & XFS_ILOG_DEV ) {
ASSERT ( whichfork = = XFS_DATA_FORK ) ;
xfs_dinode_put_rdev ( dip , ip - > i_df . if_u2 . if_rdev ) ;
}
break ;
case XFS_DINODE_FMT_UUID :
if ( iip - > ili_fields & XFS_ILOG_UUID ) {
ASSERT ( whichfork = = XFS_DATA_FORK ) ;
memcpy ( XFS_DFORK_DPTR ( dip ) ,
& ip - > i_df . if_u2 . if_uuid ,
sizeof ( uuid_t ) ) ;
}
break ;
default :
ASSERT ( 0 ) ;
break ;
}
}
/*
* Return a pointer to the extent record at file index idx .
*/
xfs_bmbt_rec_host_t *
xfs_iext_get_ext (
xfs_ifork_t * ifp , /* inode fork pointer */
xfs_extnum_t idx ) /* index of target extent */
{
ASSERT ( idx > = 0 ) ;
ASSERT ( idx < ifp - > if_bytes / sizeof ( xfs_bmbt_rec_t ) ) ;
if ( ( ifp - > if_flags & XFS_IFEXTIREC ) & & ( idx = = 0 ) ) {
return ifp - > if_u1 . if_ext_irec - > er_extbuf ;
} else if ( ifp - > if_flags & XFS_IFEXTIREC ) {
xfs_ext_irec_t * erp ; /* irec pointer */
int erp_idx = 0 ; /* irec index */
xfs_extnum_t page_idx = idx ; /* ext index in target list */
erp = xfs_iext_idx_to_irec ( ifp , & page_idx , & erp_idx , 0 ) ;
return & erp - > er_extbuf [ page_idx ] ;
} else if ( ifp - > if_bytes ) {
return & ifp - > if_u1 . if_extents [ idx ] ;
} else {
return NULL ;
}
}
/*
* Insert new item ( s ) into the extent records for incore inode
* fork ' ifp ' . ' count ' new items are inserted at index ' idx ' .
*/
void
xfs_iext_insert (
xfs_inode_t * ip , /* incore inode pointer */
xfs_extnum_t idx , /* starting index of new items */
xfs_extnum_t count , /* number of inserted items */
xfs_bmbt_irec_t * new , /* items to insert */
int state ) /* type of extent conversion */
{
xfs_ifork_t * ifp = ( state & BMAP_ATTRFORK ) ? ip - > i_afp : & ip - > i_df ;
xfs_extnum_t i ; /* extent record index */
trace_xfs_iext_insert ( ip , idx , new , state , _RET_IP_ ) ;
ASSERT ( ifp - > if_flags & XFS_IFEXTENTS ) ;
xfs_iext_add ( ifp , idx , count ) ;
for ( i = idx ; i < idx + count ; i + + , new + + )
xfs_bmbt_set_all ( xfs_iext_get_ext ( ifp , i ) , new ) ;
}
/*
* This is called when the amount of space required for incore file
* extents needs to be increased . The ext_diff parameter stores the
* number of new extents being added and the idx parameter contains
* the extent index where the new extents will be added . If the new
* extents are being appended , then we just need to ( re ) allocate and
* initialize the space . Otherwise , if the new extents are being
* inserted into the middle of the existing entries , a bit more work
* is required to make room for the new extents to be inserted . The
* caller is responsible for filling in the new extent entries upon
* return .
*/
void
xfs_iext_add (
xfs_ifork_t * ifp , /* inode fork pointer */
xfs_extnum_t idx , /* index to begin adding exts */
int ext_diff ) /* number of extents to add */
{
int byte_diff ; /* new bytes being added */
int new_size ; /* size of extents after adding */
xfs_extnum_t nextents ; /* number of extents in file */
nextents = ifp - > if_bytes / ( uint ) sizeof ( xfs_bmbt_rec_t ) ;
ASSERT ( ( idx > = 0 ) & & ( idx < = nextents ) ) ;
byte_diff = ext_diff * sizeof ( xfs_bmbt_rec_t ) ;
new_size = ifp - > if_bytes + byte_diff ;
/*
* If the new number of extents ( nextents + ext_diff )
* fits inside the inode , then continue to use the inline
* extent buffer .
*/
if ( nextents + ext_diff < = XFS_INLINE_EXTS ) {
if ( idx < nextents ) {
memmove ( & ifp - > if_u2 . if_inline_ext [ idx + ext_diff ] ,
& ifp - > if_u2 . if_inline_ext [ idx ] ,
( nextents - idx ) * sizeof ( xfs_bmbt_rec_t ) ) ;
memset ( & ifp - > if_u2 . if_inline_ext [ idx ] , 0 , byte_diff ) ;
}
ifp - > if_u1 . if_extents = ifp - > if_u2 . if_inline_ext ;
ifp - > if_real_bytes = 0 ;
}
/*
* Otherwise use a linear ( direct ) extent list .
* If the extents are currently inside the inode ,
* xfs_iext_realloc_direct will switch us from
* inline to direct extent allocation mode .
*/
else if ( nextents + ext_diff < = XFS_LINEAR_EXTS ) {
xfs_iext_realloc_direct ( ifp , new_size ) ;
if ( idx < nextents ) {
memmove ( & ifp - > if_u1 . if_extents [ idx + ext_diff ] ,
& ifp - > if_u1 . if_extents [ idx ] ,
( nextents - idx ) * sizeof ( xfs_bmbt_rec_t ) ) ;
memset ( & ifp - > if_u1 . if_extents [ idx ] , 0 , byte_diff ) ;
}
}
/* Indirection array */
else {
xfs_ext_irec_t * erp ;
int erp_idx = 0 ;
int page_idx = idx ;
ASSERT ( nextents + ext_diff > XFS_LINEAR_EXTS ) ;
if ( ifp - > if_flags & XFS_IFEXTIREC ) {
erp = xfs_iext_idx_to_irec ( ifp , & page_idx , & erp_idx , 1 ) ;
} else {
xfs_iext_irec_init ( ifp ) ;
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
erp = ifp - > if_u1 . if_ext_irec ;
}
/* Extents fit in target extent page */
if ( erp & & erp - > er_extcount + ext_diff < = XFS_LINEAR_EXTS ) {
if ( page_idx < erp - > er_extcount ) {
memmove ( & erp - > er_extbuf [ page_idx + ext_diff ] ,
& erp - > er_extbuf [ page_idx ] ,
( erp - > er_extcount - page_idx ) *
sizeof ( xfs_bmbt_rec_t ) ) ;
memset ( & erp - > er_extbuf [ page_idx ] , 0 , byte_diff ) ;
}
erp - > er_extcount + = ext_diff ;
xfs_iext_irec_update_extoffs ( ifp , erp_idx + 1 , ext_diff ) ;
}
/* Insert a new extent page */
else if ( erp ) {
xfs_iext_add_indirect_multi ( ifp ,
erp_idx , page_idx , ext_diff ) ;
}
/*
* If extent ( s ) are being appended to the last page in
* the indirection array and the new extent ( s ) don ' t fit
* in the page , then erp is NULL and erp_idx is set to
* the next index needed in the indirection array .
*/
else {
2013-10-25 10:52:44 +04:00
uint count = ext_diff ;
2013-08-12 14:49:33 +04:00
while ( count ) {
erp = xfs_iext_irec_new ( ifp , erp_idx ) ;
2013-10-25 10:52:44 +04:00
erp - > er_extcount = min ( count , XFS_LINEAR_EXTS ) ;
count - = erp - > er_extcount ;
if ( count )
2013-08-12 14:49:33 +04:00
erp_idx + + ;
}
}
}
ifp - > if_bytes = new_size ;
}
/*
* This is called when incore extents are being added to the indirection
* array and the new extents do not fit in the target extent list . The
* erp_idx parameter contains the irec index for the target extent list
* in the indirection array , and the idx parameter contains the extent
* index within the list . The number of extents being added is stored
* in the count parameter .
*
* | - - - - - - - | | - - - - - - - |
* | | | | idx - number of extents before idx
* | idx | | count |
* | | | | count - number of extents being inserted at idx
* | - - - - - - - | | - - - - - - - |
* | count | | nex2 | nex2 - number of extents after idx + count
* | - - - - - - - | | - - - - - - - |
*/
void
xfs_iext_add_indirect_multi (
xfs_ifork_t * ifp , /* inode fork pointer */
int erp_idx , /* target extent irec index */
xfs_extnum_t idx , /* index within target list */
int count ) /* new extents being added */
{
int byte_diff ; /* new bytes being added */
xfs_ext_irec_t * erp ; /* pointer to irec entry */
xfs_extnum_t ext_diff ; /* number of extents to add */
xfs_extnum_t ext_cnt ; /* new extents still needed */
xfs_extnum_t nex2 ; /* extents after idx + count */
xfs_bmbt_rec_t * nex2_ep = NULL ; /* temp list for nex2 extents */
int nlists ; /* number of irec's (lists) */
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
erp = & ifp - > if_u1 . if_ext_irec [ erp_idx ] ;
nex2 = erp - > er_extcount - idx ;
nlists = ifp - > if_real_bytes / XFS_IEXT_BUFSZ ;
/*
* Save second part of target extent list
* ( all extents past */
if ( nex2 ) {
byte_diff = nex2 * sizeof ( xfs_bmbt_rec_t ) ;
nex2_ep = ( xfs_bmbt_rec_t * ) kmem_alloc ( byte_diff , KM_NOFS ) ;
memmove ( nex2_ep , & erp - > er_extbuf [ idx ] , byte_diff ) ;
erp - > er_extcount - = nex2 ;
xfs_iext_irec_update_extoffs ( ifp , erp_idx + 1 , - nex2 ) ;
memset ( & erp - > er_extbuf [ idx ] , 0 , byte_diff ) ;
}
/*
* Add the new extents to the end of the target
* list , then allocate new irec record ( s ) and
* extent buffer ( s ) as needed to store the rest
* of the new extents .
*/
ext_cnt = count ;
ext_diff = MIN ( ext_cnt , ( int ) XFS_LINEAR_EXTS - erp - > er_extcount ) ;
if ( ext_diff ) {
erp - > er_extcount + = ext_diff ;
xfs_iext_irec_update_extoffs ( ifp , erp_idx + 1 , ext_diff ) ;
ext_cnt - = ext_diff ;
}
while ( ext_cnt ) {
erp_idx + + ;
erp = xfs_iext_irec_new ( ifp , erp_idx ) ;
ext_diff = MIN ( ext_cnt , ( int ) XFS_LINEAR_EXTS ) ;
erp - > er_extcount = ext_diff ;
xfs_iext_irec_update_extoffs ( ifp , erp_idx + 1 , ext_diff ) ;
ext_cnt - = ext_diff ;
}
/* Add nex2 extents back to indirection array */
if ( nex2 ) {
xfs_extnum_t ext_avail ;
int i ;
byte_diff = nex2 * sizeof ( xfs_bmbt_rec_t ) ;
ext_avail = XFS_LINEAR_EXTS - erp - > er_extcount ;
i = 0 ;
/*
* If nex2 extents fit in the current page , append
* nex2_ep after the new extents .
*/
if ( nex2 < = ext_avail ) {
i = erp - > er_extcount ;
}
/*
* Otherwise , check if space is available in the
* next page .
*/
else if ( ( erp_idx < nlists - 1 ) & &
( nex2 < = ( ext_avail = XFS_LINEAR_EXTS -
ifp - > if_u1 . if_ext_irec [ erp_idx + 1 ] . er_extcount ) ) ) {
erp_idx + + ;
erp + + ;
/* Create a hole for nex2 extents */
memmove ( & erp - > er_extbuf [ nex2 ] , erp - > er_extbuf ,
erp - > er_extcount * sizeof ( xfs_bmbt_rec_t ) ) ;
}
/*
* Final choice , create a new extent page for
* nex2 extents .
*/
else {
erp_idx + + ;
erp = xfs_iext_irec_new ( ifp , erp_idx ) ;
}
memmove ( & erp - > er_extbuf [ i ] , nex2_ep , byte_diff ) ;
kmem_free ( nex2_ep ) ;
erp - > er_extcount + = nex2 ;
xfs_iext_irec_update_extoffs ( ifp , erp_idx + 1 , nex2 ) ;
}
}
/*
* This is called when the amount of space required for incore file
* extents needs to be decreased . The ext_diff parameter stores the
* number of extents to be removed and the idx parameter contains
* the extent index where the extents will be removed from .
*
* If the amount of space needed has decreased below the linear
* limit , XFS_IEXT_BUFSZ , then switch to using the contiguous
* extent array . Otherwise , use kmem_realloc ( ) to adjust the
* size to what is needed .
*/
void
xfs_iext_remove (
xfs_inode_t * ip , /* incore inode pointer */
xfs_extnum_t idx , /* index to begin removing exts */
int ext_diff , /* number of extents to remove */
int state ) /* type of extent conversion */
{
xfs_ifork_t * ifp = ( state & BMAP_ATTRFORK ) ? ip - > i_afp : & ip - > i_df ;
xfs_extnum_t nextents ; /* number of extents in file */
int new_size ; /* size of extents after removal */
trace_xfs_iext_remove ( ip , idx , state , _RET_IP_ ) ;
ASSERT ( ext_diff > 0 ) ;
nextents = ifp - > if_bytes / ( uint ) sizeof ( xfs_bmbt_rec_t ) ;
new_size = ( nextents - ext_diff ) * sizeof ( xfs_bmbt_rec_t ) ;
if ( new_size = = 0 ) {
xfs_iext_destroy ( ifp ) ;
} else if ( ifp - > if_flags & XFS_IFEXTIREC ) {
xfs_iext_remove_indirect ( ifp , idx , ext_diff ) ;
} else if ( ifp - > if_real_bytes ) {
xfs_iext_remove_direct ( ifp , idx , ext_diff ) ;
} else {
xfs_iext_remove_inline ( ifp , idx , ext_diff ) ;
}
ifp - > if_bytes = new_size ;
}
/*
* This removes ext_diff extents from the inline buffer , beginning
* at extent index idx .
*/
void
xfs_iext_remove_inline (
xfs_ifork_t * ifp , /* inode fork pointer */
xfs_extnum_t idx , /* index to begin removing exts */
int ext_diff ) /* number of extents to remove */
{
int nextents ; /* number of extents in file */
ASSERT ( ! ( ifp - > if_flags & XFS_IFEXTIREC ) ) ;
ASSERT ( idx < XFS_INLINE_EXTS ) ;
nextents = ifp - > if_bytes / ( uint ) sizeof ( xfs_bmbt_rec_t ) ;
ASSERT ( ( ( nextents - ext_diff ) > 0 ) & &
( nextents - ext_diff ) < XFS_INLINE_EXTS ) ;
if ( idx + ext_diff < nextents ) {
memmove ( & ifp - > if_u2 . if_inline_ext [ idx ] ,
& ifp - > if_u2 . if_inline_ext [ idx + ext_diff ] ,
( nextents - ( idx + ext_diff ) ) *
sizeof ( xfs_bmbt_rec_t ) ) ;
memset ( & ifp - > if_u2 . if_inline_ext [ nextents - ext_diff ] ,
0 , ext_diff * sizeof ( xfs_bmbt_rec_t ) ) ;
} else {
memset ( & ifp - > if_u2 . if_inline_ext [ idx ] , 0 ,
ext_diff * sizeof ( xfs_bmbt_rec_t ) ) ;
}
}
/*
* This removes ext_diff extents from a linear ( direct ) extent list ,
* beginning at extent index idx . If the extents are being removed
* from the end of the list ( ie . truncate ) then we just need to re -
* allocate the list to remove the extra space . Otherwise , if the
* extents are being removed from the middle of the existing extent
* entries , then we first need to move the extent records beginning
* at idx + ext_diff up in the list to overwrite the records being
* removed , then remove the extra space via kmem_realloc .
*/
void
xfs_iext_remove_direct (
xfs_ifork_t * ifp , /* inode fork pointer */
xfs_extnum_t idx , /* index to begin removing exts */
int ext_diff ) /* number of extents to remove */
{
xfs_extnum_t nextents ; /* number of extents in file */
int new_size ; /* size of extents after removal */
ASSERT ( ! ( ifp - > if_flags & XFS_IFEXTIREC ) ) ;
new_size = ifp - > if_bytes -
( ext_diff * sizeof ( xfs_bmbt_rec_t ) ) ;
nextents = ifp - > if_bytes / ( uint ) sizeof ( xfs_bmbt_rec_t ) ;
if ( new_size = = 0 ) {
xfs_iext_destroy ( ifp ) ;
return ;
}
/* Move extents up in the list (if needed) */
if ( idx + ext_diff < nextents ) {
memmove ( & ifp - > if_u1 . if_extents [ idx ] ,
& ifp - > if_u1 . if_extents [ idx + ext_diff ] ,
( nextents - ( idx + ext_diff ) ) *
sizeof ( xfs_bmbt_rec_t ) ) ;
}
memset ( & ifp - > if_u1 . if_extents [ nextents - ext_diff ] ,
0 , ext_diff * sizeof ( xfs_bmbt_rec_t ) ) ;
/*
* Reallocate the direct extent list . If the extents
* will fit inside the inode then xfs_iext_realloc_direct
* will switch from direct to inline extent allocation
* mode for us .
*/
xfs_iext_realloc_direct ( ifp , new_size ) ;
ifp - > if_bytes = new_size ;
}
/*
* This is called when incore extents are being removed from the
* indirection array and the extents being removed span multiple extent
* buffers . The idx parameter contains the file extent index where we
* want to begin removing extents , and the count parameter contains
* how many extents need to be removed .
*
* | - - - - - - - | | - - - - - - - |
* | nex1 | | | nex1 - number of extents before idx
* | - - - - - - - | | count |
* | | | | count - number of extents being removed at idx
* | count | | - - - - - - - |
* | | | nex2 | nex2 - number of extents after idx + count
* | - - - - - - - | | - - - - - - - |
*/
void
xfs_iext_remove_indirect (
xfs_ifork_t * ifp , /* inode fork pointer */
xfs_extnum_t idx , /* index to begin removing extents */
int count ) /* number of extents to remove */
{
xfs_ext_irec_t * erp ; /* indirection array pointer */
int erp_idx = 0 ; /* indirection array index */
xfs_extnum_t ext_cnt ; /* extents left to remove */
xfs_extnum_t ext_diff ; /* extents to remove in current list */
xfs_extnum_t nex1 ; /* number of extents before idx */
xfs_extnum_t nex2 ; /* extents after idx + count */
int page_idx = idx ; /* index in target extent list */
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
erp = xfs_iext_idx_to_irec ( ifp , & page_idx , & erp_idx , 0 ) ;
ASSERT ( erp ! = NULL ) ;
nex1 = page_idx ;
ext_cnt = count ;
while ( ext_cnt ) {
nex2 = MAX ( ( erp - > er_extcount - ( nex1 + ext_cnt ) ) , 0 ) ;
ext_diff = MIN ( ext_cnt , ( erp - > er_extcount - nex1 ) ) ;
/*
* Check for deletion of entire list ;
* xfs_iext_irec_remove ( ) updates extent offsets .
*/
if ( ext_diff = = erp - > er_extcount ) {
xfs_iext_irec_remove ( ifp , erp_idx ) ;
ext_cnt - = ext_diff ;
nex1 = 0 ;
if ( ext_cnt ) {
ASSERT ( erp_idx < ifp - > if_real_bytes /
XFS_IEXT_BUFSZ ) ;
erp = & ifp - > if_u1 . if_ext_irec [ erp_idx ] ;
nex1 = 0 ;
continue ;
} else {
break ;
}
}
/* Move extents up (if needed) */
if ( nex2 ) {
memmove ( & erp - > er_extbuf [ nex1 ] ,
& erp - > er_extbuf [ nex1 + ext_diff ] ,
nex2 * sizeof ( xfs_bmbt_rec_t ) ) ;
}
/* Zero out rest of page */
memset ( & erp - > er_extbuf [ nex1 + nex2 ] , 0 , ( XFS_IEXT_BUFSZ -
( ( nex1 + nex2 ) * sizeof ( xfs_bmbt_rec_t ) ) ) ) ;
/* Update remaining counters */
erp - > er_extcount - = ext_diff ;
xfs_iext_irec_update_extoffs ( ifp , erp_idx + 1 , - ext_diff ) ;
ext_cnt - = ext_diff ;
nex1 = 0 ;
erp_idx + + ;
erp + + ;
}
ifp - > if_bytes - = count * sizeof ( xfs_bmbt_rec_t ) ;
xfs_iext_irec_compact ( ifp ) ;
}
/*
* Create , destroy , or resize a linear ( direct ) block of extents .
*/
void
xfs_iext_realloc_direct (
xfs_ifork_t * ifp , /* inode fork pointer */
xfs: fix the wrong new_size/rnew_size at xfs_iext_realloc_direct()
At xfs_iext_realloc_direct(), the new_size is changed by adding
if_bytes if originally the extent records are stored at the inline
extent buffer, and we have to switch from it to a direct extent
list for those new allocated extents, this is wrong. e.g,
Create a file with three extents which was showing as following,
xfs_io -f -c "truncate 100m" /xfs/testme
for i in $(seq 0 5 10); do
offset=$(($i * $((1 << 20))))
xfs_io -c "pwrite $offset 1m" /xfs/testme
done
Inline
------
irec: if_bytes bytes_diff new_size
1st 0 16 16
2nd 16 16 32
Switching
--------- rnew_size
3rd 32 16 48 + 32 = 80 roundup=128
In this case, the desired value of new_size should be 48, and then
it will be roundup to 64 and be assigned to rnew_size.
However, this issue has been covered by resetting the if_bytes to
the new_size which is calculated at the begnning of xfs_iext_add()
before leaving out this function, and in turn make the rnew_size
correctly again. Hence, this can not be detected via xfstestes.
This patch fix above problem and revise the new_size comments at
xfs_iext_realloc_direct() to make it more readable. Also, fix the
comments while switching from the inline extent buffer to a direct
extent list to reflect this change.
Signed-off-by: Jie Liu <jeff.liu@oracle.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2013-09-22 12:25:15 +04:00
int new_size ) /* new size of extents after adding */
2013-08-12 14:49:33 +04:00
{
int rnew_size ; /* real new size of extents */
rnew_size = new_size ;
ASSERT ( ! ( ifp - > if_flags & XFS_IFEXTIREC ) | |
( ( new_size > = 0 ) & & ( new_size < = XFS_IEXT_BUFSZ ) & &
( new_size ! = ifp - > if_real_bytes ) ) ) ;
/* Free extent records */
if ( new_size = = 0 ) {
xfs_iext_destroy ( ifp ) ;
}
/* Resize direct extent list and zero any new bytes */
else if ( ifp - > if_real_bytes ) {
/* Check if extents will fit inside the inode */
if ( new_size < = XFS_INLINE_EXTS * sizeof ( xfs_bmbt_rec_t ) ) {
xfs_iext_direct_to_inline ( ifp , new_size /
( uint ) sizeof ( xfs_bmbt_rec_t ) ) ;
ifp - > if_bytes = new_size ;
return ;
}
if ( ! is_power_of_2 ( new_size ) ) {
rnew_size = roundup_pow_of_two ( new_size ) ;
}
if ( rnew_size ! = ifp - > if_real_bytes ) {
ifp - > if_u1 . if_extents =
kmem_realloc ( ifp - > if_u1 . if_extents ,
rnew_size ,
ifp - > if_real_bytes , KM_NOFS ) ;
}
if ( rnew_size > ifp - > if_real_bytes ) {
memset ( & ifp - > if_u1 . if_extents [ ifp - > if_bytes /
( uint ) sizeof ( xfs_bmbt_rec_t ) ] , 0 ,
rnew_size - ifp - > if_real_bytes ) ;
}
}
xfs: fix the wrong new_size/rnew_size at xfs_iext_realloc_direct()
At xfs_iext_realloc_direct(), the new_size is changed by adding
if_bytes if originally the extent records are stored at the inline
extent buffer, and we have to switch from it to a direct extent
list for those new allocated extents, this is wrong. e.g,
Create a file with three extents which was showing as following,
xfs_io -f -c "truncate 100m" /xfs/testme
for i in $(seq 0 5 10); do
offset=$(($i * $((1 << 20))))
xfs_io -c "pwrite $offset 1m" /xfs/testme
done
Inline
------
irec: if_bytes bytes_diff new_size
1st 0 16 16
2nd 16 16 32
Switching
--------- rnew_size
3rd 32 16 48 + 32 = 80 roundup=128
In this case, the desired value of new_size should be 48, and then
it will be roundup to 64 and be assigned to rnew_size.
However, this issue has been covered by resetting the if_bytes to
the new_size which is calculated at the begnning of xfs_iext_add()
before leaving out this function, and in turn make the rnew_size
correctly again. Hence, this can not be detected via xfstestes.
This patch fix above problem and revise the new_size comments at
xfs_iext_realloc_direct() to make it more readable. Also, fix the
comments while switching from the inline extent buffer to a direct
extent list to reflect this change.
Signed-off-by: Jie Liu <jeff.liu@oracle.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Ben Myers <bpm@sgi.com>
2013-09-22 12:25:15 +04:00
/* Switch from the inline extent buffer to a direct extent list */
2013-08-12 14:49:33 +04:00
else {
if ( ! is_power_of_2 ( new_size ) ) {
rnew_size = roundup_pow_of_two ( new_size ) ;
}
xfs_iext_inline_to_direct ( ifp , rnew_size ) ;
}
ifp - > if_real_bytes = rnew_size ;
ifp - > if_bytes = new_size ;
}
/*
* Switch from linear ( direct ) extent records to inline buffer .
*/
void
xfs_iext_direct_to_inline (
xfs_ifork_t * ifp , /* inode fork pointer */
xfs_extnum_t nextents ) /* number of extents in file */
{
ASSERT ( ifp - > if_flags & XFS_IFEXTENTS ) ;
ASSERT ( nextents < = XFS_INLINE_EXTS ) ;
/*
* The inline buffer was zeroed when we switched
* from inline to direct extent allocation mode ,
* so we don ' t need to clear it here .
*/
memcpy ( ifp - > if_u2 . if_inline_ext , ifp - > if_u1 . if_extents ,
nextents * sizeof ( xfs_bmbt_rec_t ) ) ;
kmem_free ( ifp - > if_u1 . if_extents ) ;
ifp - > if_u1 . if_extents = ifp - > if_u2 . if_inline_ext ;
ifp - > if_real_bytes = 0 ;
}
/*
* Switch from inline buffer to linear ( direct ) extent records .
* new_size should already be rounded up to the next power of 2
* by the caller ( when appropriate ) , so use new_size as it is .
* However , since new_size may be rounded up , we can ' t update
* if_bytes here . It is the caller ' s responsibility to update
* if_bytes upon return .
*/
void
xfs_iext_inline_to_direct (
xfs_ifork_t * ifp , /* inode fork pointer */
int new_size ) /* number of extents in file */
{
ifp - > if_u1 . if_extents = kmem_alloc ( new_size , KM_NOFS ) ;
memset ( ifp - > if_u1 . if_extents , 0 , new_size ) ;
if ( ifp - > if_bytes ) {
memcpy ( ifp - > if_u1 . if_extents , ifp - > if_u2 . if_inline_ext ,
ifp - > if_bytes ) ;
memset ( ifp - > if_u2 . if_inline_ext , 0 , XFS_INLINE_EXTS *
sizeof ( xfs_bmbt_rec_t ) ) ;
}
ifp - > if_real_bytes = new_size ;
}
/*
* Resize an extent indirection array to new_size bytes .
*/
STATIC void
xfs_iext_realloc_indirect (
xfs_ifork_t * ifp , /* inode fork pointer */
int new_size ) /* new indirection array size */
{
int nlists ; /* number of irec's (ex lists) */
int size ; /* current indirection array size */
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
nlists = ifp - > if_real_bytes / XFS_IEXT_BUFSZ ;
size = nlists * sizeof ( xfs_ext_irec_t ) ;
ASSERT ( ifp - > if_real_bytes ) ;
ASSERT ( ( new_size > = 0 ) & & ( new_size ! = size ) ) ;
if ( new_size = = 0 ) {
xfs_iext_destroy ( ifp ) ;
} else {
ifp - > if_u1 . if_ext_irec = ( xfs_ext_irec_t * )
kmem_realloc ( ifp - > if_u1 . if_ext_irec ,
new_size , size , KM_NOFS ) ;
}
}
/*
* Switch from indirection array to linear ( direct ) extent allocations .
*/
STATIC void
xfs_iext_indirect_to_direct (
xfs_ifork_t * ifp ) /* inode fork pointer */
{
xfs_bmbt_rec_host_t * ep ; /* extent record pointer */
xfs_extnum_t nextents ; /* number of extents in file */
int size ; /* size of file extents */
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
nextents = ifp - > if_bytes / ( uint ) sizeof ( xfs_bmbt_rec_t ) ;
ASSERT ( nextents < = XFS_LINEAR_EXTS ) ;
size = nextents * sizeof ( xfs_bmbt_rec_t ) ;
xfs_iext_irec_compact_pages ( ifp ) ;
ASSERT ( ifp - > if_real_bytes = = XFS_IEXT_BUFSZ ) ;
ep = ifp - > if_u1 . if_ext_irec - > er_extbuf ;
kmem_free ( ifp - > if_u1 . if_ext_irec ) ;
ifp - > if_flags & = ~ XFS_IFEXTIREC ;
ifp - > if_u1 . if_extents = ep ;
ifp - > if_bytes = size ;
if ( nextents < XFS_LINEAR_EXTS ) {
xfs_iext_realloc_direct ( ifp , size ) ;
}
}
/*
* Free incore file extents .
*/
void
xfs_iext_destroy (
xfs_ifork_t * ifp ) /* inode fork pointer */
{
if ( ifp - > if_flags & XFS_IFEXTIREC ) {
int erp_idx ;
int nlists ;
nlists = ifp - > if_real_bytes / XFS_IEXT_BUFSZ ;
for ( erp_idx = nlists - 1 ; erp_idx > = 0 ; erp_idx - - ) {
xfs_iext_irec_remove ( ifp , erp_idx ) ;
}
ifp - > if_flags & = ~ XFS_IFEXTIREC ;
} else if ( ifp - > if_real_bytes ) {
kmem_free ( ifp - > if_u1 . if_extents ) ;
} else if ( ifp - > if_bytes ) {
memset ( ifp - > if_u2 . if_inline_ext , 0 , XFS_INLINE_EXTS *
sizeof ( xfs_bmbt_rec_t ) ) ;
}
ifp - > if_u1 . if_extents = NULL ;
ifp - > if_real_bytes = 0 ;
ifp - > if_bytes = 0 ;
}
/*
* Return a pointer to the extent record for file system block bno .
*/
xfs_bmbt_rec_host_t * /* pointer to found extent record */
xfs_iext_bno_to_ext (
xfs_ifork_t * ifp , /* inode fork pointer */
xfs_fileoff_t bno , /* block number to search for */
xfs_extnum_t * idxp ) /* index of target extent */
{
xfs_bmbt_rec_host_t * base ; /* pointer to first extent */
xfs_filblks_t blockcount = 0 ; /* number of blocks in extent */
xfs_bmbt_rec_host_t * ep = NULL ; /* pointer to target extent */
xfs_ext_irec_t * erp = NULL ; /* indirection array pointer */
int high ; /* upper boundary in search */
xfs_extnum_t idx = 0 ; /* index of target extent */
int low ; /* lower boundary in search */
xfs_extnum_t nextents ; /* number of file extents */
xfs_fileoff_t startoff = 0 ; /* start offset of extent */
nextents = ifp - > if_bytes / ( uint ) sizeof ( xfs_bmbt_rec_t ) ;
if ( nextents = = 0 ) {
* idxp = 0 ;
return NULL ;
}
low = 0 ;
if ( ifp - > if_flags & XFS_IFEXTIREC ) {
/* Find target extent list */
int erp_idx = 0 ;
erp = xfs_iext_bno_to_irec ( ifp , bno , & erp_idx ) ;
base = erp - > er_extbuf ;
high = erp - > er_extcount - 1 ;
} else {
base = ifp - > if_u1 . if_extents ;
high = nextents - 1 ;
}
/* Binary search extent records */
while ( low < = high ) {
idx = ( low + high ) > > 1 ;
ep = base + idx ;
startoff = xfs_bmbt_get_startoff ( ep ) ;
blockcount = xfs_bmbt_get_blockcount ( ep ) ;
if ( bno < startoff ) {
high = idx - 1 ;
} else if ( bno > = startoff + blockcount ) {
low = idx + 1 ;
} else {
/* Convert back to file-based extent index */
if ( ifp - > if_flags & XFS_IFEXTIREC ) {
idx + = erp - > er_extoff ;
}
* idxp = idx ;
return ep ;
}
}
/* Convert back to file-based extent index */
if ( ifp - > if_flags & XFS_IFEXTIREC ) {
idx + = erp - > er_extoff ;
}
if ( bno > = startoff + blockcount ) {
if ( + + idx = = nextents ) {
ep = NULL ;
} else {
ep = xfs_iext_get_ext ( ifp , idx ) ;
}
}
* idxp = idx ;
return ep ;
}
/*
* Return a pointer to the indirection array entry containing the
* extent record for filesystem block bno . Store the index of the
* target irec in * erp_idxp .
*/
xfs_ext_irec_t * /* pointer to found extent record */
xfs_iext_bno_to_irec (
xfs_ifork_t * ifp , /* inode fork pointer */
xfs_fileoff_t bno , /* block number to search for */
int * erp_idxp ) /* irec index of target ext list */
{
xfs_ext_irec_t * erp = NULL ; /* indirection array pointer */
xfs_ext_irec_t * erp_next ; /* next indirection array entry */
int erp_idx ; /* indirection array index */
int nlists ; /* number of extent irec's (lists) */
int high ; /* binary search upper limit */
int low ; /* binary search lower limit */
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
nlists = ifp - > if_real_bytes / XFS_IEXT_BUFSZ ;
erp_idx = 0 ;
low = 0 ;
high = nlists - 1 ;
while ( low < = high ) {
erp_idx = ( low + high ) > > 1 ;
erp = & ifp - > if_u1 . if_ext_irec [ erp_idx ] ;
erp_next = erp_idx < nlists - 1 ? erp + 1 : NULL ;
if ( bno < xfs_bmbt_get_startoff ( erp - > er_extbuf ) ) {
high = erp_idx - 1 ;
} else if ( erp_next & & bno > =
xfs_bmbt_get_startoff ( erp_next - > er_extbuf ) ) {
low = erp_idx + 1 ;
} else {
break ;
}
}
* erp_idxp = erp_idx ;
return erp ;
}
/*
* Return a pointer to the indirection array entry containing the
* extent record at file extent index * idxp . Store the index of the
* target irec in * erp_idxp and store the page index of the target
* extent record in * idxp .
*/
xfs_ext_irec_t *
xfs_iext_idx_to_irec (
xfs_ifork_t * ifp , /* inode fork pointer */
xfs_extnum_t * idxp , /* extent index (file -> page) */
int * erp_idxp , /* pointer to target irec */
int realloc ) /* new bytes were just added */
{
xfs_ext_irec_t * prev ; /* pointer to previous irec */
xfs_ext_irec_t * erp = NULL ; /* pointer to current irec */
int erp_idx ; /* indirection array index */
int nlists ; /* number of irec's (ex lists) */
int high ; /* binary search upper limit */
int low ; /* binary search lower limit */
xfs_extnum_t page_idx = * idxp ; /* extent index in target list */
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
ASSERT ( page_idx > = 0 ) ;
ASSERT ( page_idx < = ifp - > if_bytes / sizeof ( xfs_bmbt_rec_t ) ) ;
ASSERT ( page_idx < ifp - > if_bytes / sizeof ( xfs_bmbt_rec_t ) | | realloc ) ;
nlists = ifp - > if_real_bytes / XFS_IEXT_BUFSZ ;
erp_idx = 0 ;
low = 0 ;
high = nlists - 1 ;
/* Binary search extent irec's */
while ( low < = high ) {
erp_idx = ( low + high ) > > 1 ;
erp = & ifp - > if_u1 . if_ext_irec [ erp_idx ] ;
prev = erp_idx > 0 ? erp - 1 : NULL ;
if ( page_idx < erp - > er_extoff | | ( page_idx = = erp - > er_extoff & &
realloc & & prev & & prev - > er_extcount < XFS_LINEAR_EXTS ) ) {
high = erp_idx - 1 ;
} else if ( page_idx > erp - > er_extoff + erp - > er_extcount | |
( page_idx = = erp - > er_extoff + erp - > er_extcount & &
! realloc ) ) {
low = erp_idx + 1 ;
} else if ( page_idx = = erp - > er_extoff + erp - > er_extcount & &
erp - > er_extcount = = XFS_LINEAR_EXTS ) {
ASSERT ( realloc ) ;
page_idx = 0 ;
erp_idx + + ;
erp = erp_idx < nlists ? erp + 1 : NULL ;
break ;
} else {
page_idx - = erp - > er_extoff ;
break ;
}
}
* idxp = page_idx ;
* erp_idxp = erp_idx ;
return ( erp ) ;
}
/*
* Allocate and initialize an indirection array once the space needed
* for incore extents increases above XFS_IEXT_BUFSZ .
*/
void
xfs_iext_irec_init (
xfs_ifork_t * ifp ) /* inode fork pointer */
{
xfs_ext_irec_t * erp ; /* indirection array pointer */
xfs_extnum_t nextents ; /* number of extents in file */
ASSERT ( ! ( ifp - > if_flags & XFS_IFEXTIREC ) ) ;
nextents = ifp - > if_bytes / ( uint ) sizeof ( xfs_bmbt_rec_t ) ;
ASSERT ( nextents < = XFS_LINEAR_EXTS ) ;
erp = kmem_alloc ( sizeof ( xfs_ext_irec_t ) , KM_NOFS ) ;
if ( nextents = = 0 ) {
ifp - > if_u1 . if_extents = kmem_alloc ( XFS_IEXT_BUFSZ , KM_NOFS ) ;
} else if ( ! ifp - > if_real_bytes ) {
xfs_iext_inline_to_direct ( ifp , XFS_IEXT_BUFSZ ) ;
} else if ( ifp - > if_real_bytes < XFS_IEXT_BUFSZ ) {
xfs_iext_realloc_direct ( ifp , XFS_IEXT_BUFSZ ) ;
}
erp - > er_extbuf = ifp - > if_u1 . if_extents ;
erp - > er_extcount = nextents ;
erp - > er_extoff = 0 ;
ifp - > if_flags | = XFS_IFEXTIREC ;
ifp - > if_real_bytes = XFS_IEXT_BUFSZ ;
ifp - > if_bytes = nextents * sizeof ( xfs_bmbt_rec_t ) ;
ifp - > if_u1 . if_ext_irec = erp ;
return ;
}
/*
* Allocate and initialize a new entry in the indirection array .
*/
xfs_ext_irec_t *
xfs_iext_irec_new (
xfs_ifork_t * ifp , /* inode fork pointer */
int erp_idx ) /* index for new irec */
{
xfs_ext_irec_t * erp ; /* indirection array pointer */
int i ; /* loop counter */
int nlists ; /* number of irec's (ex lists) */
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
nlists = ifp - > if_real_bytes / XFS_IEXT_BUFSZ ;
/* Resize indirection array */
xfs_iext_realloc_indirect ( ifp , + + nlists *
sizeof ( xfs_ext_irec_t ) ) ;
/*
* Move records down in the array so the
* new page can use erp_idx .
*/
erp = ifp - > if_u1 . if_ext_irec ;
for ( i = nlists - 1 ; i > erp_idx ; i - - ) {
memmove ( & erp [ i ] , & erp [ i - 1 ] , sizeof ( xfs_ext_irec_t ) ) ;
}
ASSERT ( i = = erp_idx ) ;
/* Initialize new extent record */
erp = ifp - > if_u1 . if_ext_irec ;
erp [ erp_idx ] . er_extbuf = kmem_alloc ( XFS_IEXT_BUFSZ , KM_NOFS ) ;
ifp - > if_real_bytes = nlists * XFS_IEXT_BUFSZ ;
memset ( erp [ erp_idx ] . er_extbuf , 0 , XFS_IEXT_BUFSZ ) ;
erp [ erp_idx ] . er_extcount = 0 ;
erp [ erp_idx ] . er_extoff = erp_idx > 0 ?
erp [ erp_idx - 1 ] . er_extoff + erp [ erp_idx - 1 ] . er_extcount : 0 ;
return ( & erp [ erp_idx ] ) ;
}
/*
* Remove a record from the indirection array .
*/
void
xfs_iext_irec_remove (
xfs_ifork_t * ifp , /* inode fork pointer */
int erp_idx ) /* irec index to remove */
{
xfs_ext_irec_t * erp ; /* indirection array pointer */
int i ; /* loop counter */
int nlists ; /* number of irec's (ex lists) */
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
nlists = ifp - > if_real_bytes / XFS_IEXT_BUFSZ ;
erp = & ifp - > if_u1 . if_ext_irec [ erp_idx ] ;
if ( erp - > er_extbuf ) {
xfs_iext_irec_update_extoffs ( ifp , erp_idx + 1 ,
- erp - > er_extcount ) ;
kmem_free ( erp - > er_extbuf ) ;
}
/* Compact extent records */
erp = ifp - > if_u1 . if_ext_irec ;
for ( i = erp_idx ; i < nlists - 1 ; i + + ) {
memmove ( & erp [ i ] , & erp [ i + 1 ] , sizeof ( xfs_ext_irec_t ) ) ;
}
/*
* Manually free the last extent record from the indirection
* array . A call to xfs_iext_realloc_indirect ( ) with a size
* of zero would result in a call to xfs_iext_destroy ( ) which
* would in turn call this function again , creating a nasty
* infinite loop .
*/
if ( - - nlists ) {
xfs_iext_realloc_indirect ( ifp ,
nlists * sizeof ( xfs_ext_irec_t ) ) ;
} else {
kmem_free ( ifp - > if_u1 . if_ext_irec ) ;
}
ifp - > if_real_bytes = nlists * XFS_IEXT_BUFSZ ;
}
/*
* This is called to clean up large amounts of unused memory allocated
* by the indirection array . Before compacting anything though , verify
* that the indirection array is still needed and switch back to the
* linear extent list ( or even the inline buffer ) if possible . The
* compaction policy is as follows :
*
* Full Compaction : Extents fit into a single page ( or inline buffer )
* Partial Compaction : Extents occupy less than 50 % of allocated space
* No Compaction : Extents occupy at least 50 % of allocated space
*/
void
xfs_iext_irec_compact (
xfs_ifork_t * ifp ) /* inode fork pointer */
{
xfs_extnum_t nextents ; /* number of extents in file */
int nlists ; /* number of irec's (ex lists) */
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
nlists = ifp - > if_real_bytes / XFS_IEXT_BUFSZ ;
nextents = ifp - > if_bytes / ( uint ) sizeof ( xfs_bmbt_rec_t ) ;
if ( nextents = = 0 ) {
xfs_iext_destroy ( ifp ) ;
} else if ( nextents < = XFS_INLINE_EXTS ) {
xfs_iext_indirect_to_direct ( ifp ) ;
xfs_iext_direct_to_inline ( ifp , nextents ) ;
} else if ( nextents < = XFS_LINEAR_EXTS ) {
xfs_iext_indirect_to_direct ( ifp ) ;
} else if ( nextents < ( nlists * XFS_LINEAR_EXTS ) > > 1 ) {
xfs_iext_irec_compact_pages ( ifp ) ;
}
}
/*
* Combine extents from neighboring extent pages .
*/
void
xfs_iext_irec_compact_pages (
xfs_ifork_t * ifp ) /* inode fork pointer */
{
xfs_ext_irec_t * erp , * erp_next ; /* pointers to irec entries */
int erp_idx = 0 ; /* indirection array index */
int nlists ; /* number of irec's (ex lists) */
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
nlists = ifp - > if_real_bytes / XFS_IEXT_BUFSZ ;
while ( erp_idx < nlists - 1 ) {
erp = & ifp - > if_u1 . if_ext_irec [ erp_idx ] ;
erp_next = erp + 1 ;
if ( erp_next - > er_extcount < =
( XFS_LINEAR_EXTS - erp - > er_extcount ) ) {
memcpy ( & erp - > er_extbuf [ erp - > er_extcount ] ,
erp_next - > er_extbuf , erp_next - > er_extcount *
sizeof ( xfs_bmbt_rec_t ) ) ;
erp - > er_extcount + = erp_next - > er_extcount ;
/*
* Free page before removing extent record
* so er_extoffs don ' t get modified in
* xfs_iext_irec_remove .
*/
kmem_free ( erp_next - > er_extbuf ) ;
erp_next - > er_extbuf = NULL ;
xfs_iext_irec_remove ( ifp , erp_idx + 1 ) ;
nlists = ifp - > if_real_bytes / XFS_IEXT_BUFSZ ;
} else {
erp_idx + + ;
}
}
}
/*
* This is called to update the er_extoff field in the indirection
* array when extents have been added or removed from one of the
* extent lists . erp_idx contains the irec index to begin updating
* at and ext_diff contains the number of extents that were added
* or removed .
*/
void
xfs_iext_irec_update_extoffs (
xfs_ifork_t * ifp , /* inode fork pointer */
int erp_idx , /* irec index to update */
int ext_diff ) /* number of new extents */
{
int i ; /* loop counter */
int nlists ; /* number of irec's (ex lists */
ASSERT ( ifp - > if_flags & XFS_IFEXTIREC ) ;
nlists = ifp - > if_real_bytes / XFS_IEXT_BUFSZ ;
for ( i = erp_idx ; i < nlists ; i + + ) {
ifp - > if_u1 . if_ext_irec [ i ] . er_extoff + = ext_diff ;
}
}