Ext2 Number of Data Blocks
In Ext2, an FFS derivative, not all blocks associated with a file contains data. Some are used for storing pointers to the data blocks, called as indirect, double and triple indirect blocks. Finding actual number of data blocks from a given number of blocks associated with an inode in O(1) time I thought, wouldn't be that time-consuming. I was wrong. After spending almost entire evening few weeks ago trying to code it, today again I had to fix it for it wasn't correct! This is the fixed version, as of today:
Userspace version. When I'II take technical interview next, this will be one of the questions :)
/*
* Do not count the {,double,triple} indirect blocks.
* While calculating ceiling, the result is subtracted because
* it is to be counted as extra number of blocks.
*
* [FIXME] This code hates sparse files, and the math is ugly
*/
blkcnt_t block_count(struct inode *inode)
{
blkcnt_t blks, dind, tind, diff = 0, level, rem;
int addrs_per_block = (inode->i_sb->s_blocksize / sizeof(blkcnt_t));
/* see DQUOT_ALLOC_SPACE_NODIRTY and inode_add_bytes */
blks = inode->i_blocks >> (inode->i_sb->s_blocksize_bits - 9);
/* if has an indirect block */
if (blks > EXT2_IND_BLOCK)
diff++;
dind = EXT2_IND_BLOCK + addrs_per_block;
if (blks >= dind) {
diff++; /* the double indirect block */
if ((blks - dind - diff) < (addrs_per_block * addrs_per_block + addrs_per_block)) {
rem = (blks - dind - diff) / addrs_per_block;
/* calculate ceiling */
if ((blks - dind - diff - rem) > addrs_per_block * rem)
rem++;
diff += rem; /* indirects */
}
else
diff += addrs_per_block; /* indirects */
}
tind = dind + (addrs_per_block * addrs_per_block);
if (blks >= tind + diff) {
diff++; /* the triple indirect block */
/* calculate, number of double indirect blocks */
level = (blks - tind - diff) / (addrs_per_block * addrs_per_block);
diff += level * addrs_per_block; /* indirects */
/* calculate ceiling, here 'rem' stores remaining blocks */
rem = blks - tind - diff - level - (addrs_per_block * addrs_per_block * level);
if (rem > 0) {
level++;
rem--;
}
diff += level; /* double indirects */
/* for remaining blocks, calculate number of indirect blocks used */
if (rem > 0) {
level = rem / addrs_per_block;
if ((rem - level) > level * addrs_per_block)
level++;
diff += level; /* indirects */
}
}
return blks - diff;
}
Userspace version. When I'II take technical interview next, this will be one of the questions :)