In this section, we shall show how to access and display the contents of EXT2 file systems by example programs. In order to compile and run these programs, the system must have the ext2fs.h header file installed, which defines the data structures of EXT2/3/4 file systems. Ubuntu Linux user may get and install the ext2fs development package by
sudo apt-get install ext2fs-dev
1. Display Superblock
The following C program displays the superblock of an EXT2 file system. The basic technique is as follows.
- Open vdisk for READ: int fd = open(“vdisk”, O_RDONLY);
- Read the superblock (block #1 or 1 KB at offset 1024) into a char buf[1024].
char buf[1024];
lseek(fd, 1024, SEEK_SET); // seek to byte offset 1024
int n = read(fd, buf, 1024);
- Let a struct ext2_super_block *sp point to buf[ ]. Then use sp- > field to access the various fields of the superblock structure.
struct ext2_super_block *sp = (struct ext2_super_block *)buf;
printf(“s_magic = %x\n”, sp->s_magic) // print s_magic
printf(“s_inodes_count = %d\n”, sp->s_inodes_count); // print s_inodes_ count etc.
The same technique is also applicable to any other data structures in a real or virtual disk.
Example 11.1 superblock.c program: display superblock information of an EXT2 file system.
/*********** superblock.c program ************/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ext2fs/ext2_fs.h>
// typedef u8, u16, u32 SUPER for convenience
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef struct ext2_super_block SUPER;
SUPER *sp;
char buf[1024];
int fd, blksize, inodesize;
int print(char *s, u32 x)
{
printf(“%-30s = %8d\n”, s, x);
}
int super(char *device)
{
fd = open(device, O_RDONLY);
if (fd < 0){
printf(“open %sfailed\n”, device); exit(1);
}
lseek(fd, (long)1024*1, 0); // block 1 on FD, offset 1024 on HD
read(fd, buf, 1024);
sp = (SUPER *)buf; // as a super block structure
// check EXT2 FS magic number:
printf(“%-30s = %8x “, “s_magic”, sp->s_magic);
if (sp->s_magic != 0xEF53){
printf(“NOT an EXT2 FS\n”);
exit(2);
}
printf(“EXT2 FS OK\n”);
print(“s_inodes_count”, sp->s_inodes_count);
print(“s_blocks_count”, sp->s_blocks_count);
print(“s_r_blocks_count”, sp->s_r_blocks_count);
print(“s_free_inodes_count”, sp->s_free_inodes_count);
print(“s_free_blocks_count”, sp->s_free_blocks_count);
print(“s_first_data_block”, sp->s_first_data_block);
print(“s_log_block_size”, sp->s_log_block_size);
print(“s_blocks_per_group”, sp->s_blocks_per_group);
print(“s_inodes_per_group”, sp->s_inodes_per_group);
print(“s_mnt_count”, sp->s_mnt_count);
print(“s_max_mnt_count”, sp->s_max_mnt_count);
printf(“%-30s = %8x\n”, “s_magic”, sp->s_magic);
printf(“s_mtime = %s”, ctime(&sp->s_mtime));
printf(“s_wtime = %s”, ctime(&sp->s_wtime));
blksize = 1024 * (1 << sp->s_log_block_size);
printf(“block size = %d\n”, blksize);
printf(“inode size = %d\n”, sp->s_inode_size);
}
char *device = “mydisk”; // default device name
int main(int argc, char *argv[])
{
if (argc>1)
device = argv[1];
super(device);
}
Figure 11.2 shows the outputs of running the superblock.c program
Fig. 11.2 Superblock of Ext2 file system
Exercise 11.1 Write a C program to display the group descriptor of an EXT2 file system on a device. (See Problem 1).
2. Display Bitmaps
The program in Example 11.2 displays the inodes bitmap (imap) in HEX.
Example 11.2 imap.c program: display inodes bitmap of an EXT2 file system.
/*************** imap c program **************/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ext2fs/ext2_fs.h>
typedef struct ext2_super_block SUPER;
typedef struct ext2_group_desc GD;
#define BLKSIZE 1024
SUPER *sp;
GD *gp;
char buf[BLKSIZE];
int fd;
// get_block() reads a disk block into a buf[ ]
int get_block(int fd, int blk, char *buf)
{
lseek(fd, (long)blk*BLKSIZE, SEEK_SET);
return read(fd, buf, BLKSIZE);
}
int imap(char *device)
{
int i, ninodes, blksize, imapblk;
fd = open(dev, O_RDONLY);
if (fd < 0){printf(“open %s failed\n”, device); exit(1);}
get_block(fd, 1, buf); // get superblock
sp = (SUPER *)buf;
// check magic number to ensure it’s an EXT2 FS
ninodes = sp->s_inodes_count; // get inodes_count
printf(“ninodes = %d\n”, ninodes);
get_block(fd, 2, buf); // get group descriptor
gp = (GD *)buf;
imapblk = gp->bg_inode_bitmap; // get imap block number
printf(“imapblk = %d\n”, imapblk);
get_block(fd, imapblk, buf); // get imap block into buf[ ]
for (i=0; i<=nidoes/8; i++){ // print each byte in HEX
printf(”%02x ”, (u8)buf[i]);
}
printf(“\n”);
}
char * dev=”mydisk”; // default device
int main(int argc, char *argv[ ] )
{
if (argc>1) dev = argv[1];
imap(dev);
}
The program prints each byte of the inodes bitmap as 2 HEX digits. The outputs look like the following.
|<————— niodes = 184 bits (23 bytes)————————|
ff 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff
In the imap, bits are stored linearly from low to high address. The first 16 bits (from low to high) are b’11111111 11100000’, but they are printed as ff 07 in HEX, which is not very informative since the bits in each byte are printed in reverse order, i.e. from high to low address.
Exercise 11.2 Modify the imap.c program to print the inodes bitmap in char map form, i.e. for each bit, print a ‘0’ if the bit is 0, print a ‘1’ if the bit is 1. (see Problem 2).
The outputs of the modified program should look like Fig. 11.3, in which each char represents a bit in the imap.
Exercise 11.3 Write a C program to display the blocks bitmap of an Ext2 file system, also in char map form.
3. Display Root Inode
In an EXT2 file system, the number 2 (count from 1) inode is the inode of the root directory /. If we read the root inode into memory, we should be able to display its various fields such as mode, uid, gid, file size, time fields, hard links count and data block numbers, etc. The program in Example 11.3 displays the INDOE information of the root directory of an EXT2 file system.
Example 11.3 inode.c program: display root inode information of an EXT2 file system.
/*********** inode c file **********/
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <ext2fs/ext2_fs.h>
#define BLKSIZE 1024
typedef struct ext2_group_desc GD;
typedef struct ext2_super_block SUPER;
typedef struct ext2_inode INODE;
typedef struct ext2_dir_entry_2 DIR;
SUPER *sp;
GD *gp;
INODE *ip;
DIR *dp;
char buf[BLKSIZE];
int fd, firstdata, inodesize, blksize, iblock;
char *dev = “mydisk”;
int get_block(int fd, int blk, char *buf)
{
lseek(fd, blk*BLKSIZE, SEEK_SET);
return read(fd, buf, BLKSIZE);
}
int inode(char *dev)
{
int i;
fd = open(dev, O_RDONLY); if (fd < 0){
printf(“open failed\n”); exit(1);
}
/*************************************
same code as before to check EXT2 FS
**************************************/
get_block(fd, 2, buf); // get group descriptor
gp = (GD *)buf;
printf(“bmap_block=%d imap_block=%d inodes_table=%d “,
gp->bg_block_bitmap,
gp->bg_inode_bitmap,
gp->bg_inode_table,
iblock = gp->bg_inode_table;
printf(“— root inode information—– \n”);
get_block(fd, iblock, buf);
ip = (INODE *)buf;
ip++; // ip point at #2 INODE
printf(“mode = %4x “, ip->i_mode);
printf(“uid = %d gid = %d\n”, ip->i_uid, ip->i_gid);
printf(“size = %d\n”, ip->i_size);
printf(“ctime = %s”, ctime(&ip->i_ctime));
printf(“links = %d\n”, ip->i_links_count);
for (i=0; i<15; i++){ // print disk block numbers
if (ip->i_block[i]) // print non-zero blocks only
printf(“i_block[%d] = %d\n”, i, ip->i_block[i]);
}
}
int main(int argc, char *argv[ ])
{
if (argc>1) dev = argv[1];
inode(dev);
}
Figure 11.4 shows the root inode of an EXT2 file system.
In Fig. 11.4, i_mode = 0x41ed or b’0100 0001 1110 1101′ in binary. The first 4 bits 0100 is the file type (DIRectory). The next 3 bits 000 = ugs are all 0’s, meaning the file has no special usage, e.g. it is not a setuid program. The last 9 bits can be divided into 3 groups 111,101,101, which are the rwx permission bits of the file’s owner, same group as owner and others. For regular files, x bit = 1 means the file is executable. For directories, x bit = 1 means access to (i.e. cd into) the directory is allowed; a 0 x bit means no access to the directory.
4. Display Directory Entries
Each data block of a directory INODE contains dir_entries, which are.
struct ext2_dir_entry_2 {
u32 inode;
u16 rec_len;
u8 name_len;
u8 file_type;
char name[EXT2_NAME_LEN];
};
The contents of each data block of a directory has the form
[inode rec_len name_len NAME] [inode rec_len name_len NAME] ……
where NAME is a sequence of name_len chars without a terminating NULL byte. Each dir_entry has a record length rec_len. The rec_len of the last entry in a block covers the remaining block length, i.e. from where the entry begins to the end of block. The following algorithm shows how to step through the dir_entries in a directory data block.
/******** Algorithm to step through entries in a DIR data block ********/
struct ext2_dir_entry_2 *dp; // dir_entry pointer
char *cp; // char pointer
int blk = a data block (number) of a DIR (e.g. i_block[0]);
char buf[BLKSIZE], temp[256];
get_block(fd, blk, buf); // get data block into buf[ ]
dp = (struct ext2_dir_entry_2 *)buf; // as dir_entry
cp = buf;
while(cp < buf + BLKSIZE){
strncpy(temp, dp->name, dp->name_len); // make name a string
temp[dp->name_len] =0; // ensure NULL at end
printf(“%d %d %d %s\n”, dp->inode, dp->rec_len, dp->name_len, temp);
cp += dp->rec_len; // advance cp by rec_len
dp = (struct ext2_dir_entry_2 *)cp; // pull dp to next entry
}
Exercise 11.4 Write a C program to print the dir_entries of a directory. For simplicity, we may assume a directory INODE has at most 12 direct blocks, i_block[0] to i_block[11]. This assumption is reasonable. With 1 KB block size and an average file name length of 16 chars, a single disk block can contain up to 1024/(8 + 16) = 42 dir_entries. With 12 disk blocks, a directory can contain more than 500 entries. We may safely assume that no user would put that many files in any directory. For an empty EXT2 file system, the program output should look like Fig. 11.5. (see Problem 4).
Exercise 11.5 Mount mydisk under Linux. Create new directories and copy files to the mounted file system, then umount it (See Problem 5). Run the dir.c program on mydisk again to see the outputs, which should look like Fig. 11.6. The reader may verify that the name_len of each entry is the exact number of chars in the name field, and every rec_len is a multiple of 4 (for alignment), which is (8 + name_len) raised to the next multiple of 4, except the last entry, whose rec_len covers the remaining block length.
Exercise 11.6 Given an INODE pointer to a DIRectory inode. Write a
int search(INODE *dir, char *name)
function which searches for a dir_entry with a given name. Return its inode number if found, else return 0. (see Problem 6).
Source: Wang K.C. (2018), Systems Programming in Unix/Linux, Springer; 1st ed. 2018 edition.