Last Block on an Ext2 Inode

Submitted by jcochett
on August 13, 2006 - 12:39am

This was a very complex task to complete. An even now I am not 100% sure that my solution is correct. The task was is to get a pointer to the the last block of data in an ext2 inode. This means I need to transverse the direct and indirect blocks of the ext2 inode. It took me a solid week and some serious brain segment faults but I think I have my solution. If you are reading this and know a better way, please post a comment.

The first concept was to search from begining to end. But to do this is a serious waste of time and cpu. The second concept was to start from the end and work my way up to the begining, skipping any indirect pointers that were zero. That means if the triple indirect pointer was zero skip it and look at the double indirect pointer. But this was still a waste of cpu cycles. Then I re-found the i_blocks variable in the ext2 inode structure. It states that:

include/linux/ext2_fs.h
   __le32  i_blocks;                /* Blocks count */

Blocks count is the number of blocks that inode is using. So all I would have to do is calculate where that block is. I could not find code that did this within the linux kernel. After a quick glance at the code, I decided to knock it out myself real quick. That was 8 days ago.

Lets start with i_blocks, it really is the number of blocks of size 512, but the block size of the standard installition of my linux is 4096. So the number of blocks (bn) is really i_blocks divided by eight.

bn = i_blocks / 8;

Next we know that the ext2 inode has direct blocks, indirect blocks, double indirect blocks, and triple indirect blocks. They are all contained in the i_block structure.

#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK   EXT2_NDIR_BLOCKS      // 12
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)   // 13
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)  // 14
#define EXT2_N_BLOCKS   (EXT2_TIND_BLOCK + 1)  // 15
   __le32  i_block[EXT2_N_BLOCKS];  /* Pointers to blocks */

So from i_block[0] to i_block[11] are the direct blocks, i_block[12] is the indirect block pointer, i_block[13] is the double indirect pointer, and i_block[14] is the triple indirect pointer. What this means that that the block number if greater than 12 is offset by 12 within each indirect section. The indirect block group holds 4096 pointers, the double indirect holds 4096 x 4096 pointers, and the triple indirect holds 4096 x 4096 x 4096 pointers. To normalize the we have to add EXT2_NDIR_BLOCKS plus the previous indirect size as needed to the block number (bn) defined above. Thus we get the normalized block number (nbn).

if (bn < db)                             // direct
   nbn = bn;
else if (bn < 4096 + db)                 // indirect
   nbn = bn – db;
else if (bn < (4096 << 12) + 4096 + db)  // double indirect
   nbn = bn – pagesize – db;
else                                     // triple indirect
   nbn = bn – (4096 << 12) – 4096 – db;

Haveing the normalize block number allows us to calculate the indirect, double indirect, and triple indirect index.

if (bn < db)                              // direct
{
   index = bn;
}
else if (bn < 4096 + db)              // indirect
{
   nbn = bn – db;
   index = nbn;
}
else if (bn < (4096 << 12) + 4096 + db)  // double indirect
{
   nbn = bn – 4096 – db;
   doubleindex = nbn / 4096;
   index = nbn % 4096;
}
else                                      // triple indirect
{
   nbn = bn – (4096 << 12) – 4096 – db;
   tripleindex = nbn / (4096 << 12);
   doubleindex = nbn / 4096;
   index = nbn % 4096;
}

If these indexes calculations are correct then we can pull out the block.

struct buffer_head *bh;
char *lbp;  // Last block pointer

if (bn < db)                              // direct
{
   index = bn;
   bh = sb_bread(sb, e->block[index]);
}
else if (bn < 4096 + db)              // indirect
{
   nbn = bn – db;
   index = nbn;
   bh = sb_bread(sb, e->block[EXT2_IND_BLOCK]);
   bh = sb_bread(sb, ((__u32 *)bh->b_data)[index]));
}
else if (bn < (4096 << 12) + 4096 + db)  // double indirect
{
   nbn = bn – 4096 – db;
   doubleindex = nbn / 4096;
   index = nbn % 4096;
   bh = sb_bread(sb, e->block[EXT2_DIND_BLOCK]);
   bh = sb_bread(sb, ((__u32 *)bh->b_data)[doubleindex]));
   bh = sb_bread(sb, ((__u32 *)bh->b_data)[index]));
}
else                                      // triple indirect
{
   nbn = bn – (4096 << 12) – 4096 – db;
   tripleindex = nbn / (4096 << 12);
   doubleindex = nbn / 4096;
   index = nbn % 4096;
   bh = sb_bread(sb, e->block[EXT2_TIND_BLOCK]);
   bh = sb_bread(sb, ((__u32 *)bh->b_data)[tripleindex]));
   bh = sb_bread(sb, ((__u32 *)bh->b_data)[doubleindex]));
   bh = sb_bread(sb, ((__u32 *)bh->b_data)[index]));
}

lbp = bh->b_data;

Well that is it. I am still checking to see if it works.

i_blocks Bad!

Franernelstein (not verified)
on
August 15, 2006 - 2:17pm


Grrrrr, i_block assumption Bad! i_blocks number of blocks reserved for file! Not actual number blocks! Frankernelstein Mad!


AAARRRGGGgggghhh, name misp

Frankernelstein (not verified)
on
August 15, 2006 - 2:20pm


AAARRRGGGgggghhh, name mispelled on last post! Frankernelstein spelling Bad, spell checker Good!

i_blocks

on
August 15, 2006 - 9:05pm

Yes this is correct. i_blocks is what ext2 decides to reserve for the inode, not the actual number in use. Instead of i_blocks use this:

//struct super_block *sb 
//struct ext2_inode *e

bs = sb->s_blocksize;

if (e->i_size)
   bn = (e->i_size / bs) + (e->i_size % bs ? 1 : 0) - 1;
else
   bn = 0;

Like before the result in bn is the "index". Meaning this number is from 0 to (n - 1). The if statement covers the special case for a zero sized file.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.