File Operations in Unix/Linux: Programming Examples

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 in Example 7.1 displays the superblock of an EXT2 file system. The basic technique is to read the superblock (block #1 or 1KB at offset 1024) into a char buf[1024]. Let a struct ext2_super_block *p point to buf[ ]. Then use p->field to access the various fields of the superblock structure. The technique is similar to that of accessing partition tables in the MBR.

Example 7.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 or offset 1024

read(fd, buf, 1024);

sp = (SUPER *)buf;            // as a super block structure

// check for 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_blcok”,  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 7.5 shows the outputs of running the super.c example program.

Exercise 7.4: Write a C program to display the group descriptor of an EXT2 file system on a device.

2. Display Bitmaps

The C program in Example 7.2 display the inodes bitmap (imap) in HEX form.

Example 7.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

typedef struct ext2_group_desc

#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, 0);

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;

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 “, (unsigned char)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 are printed in reverse order, i.e. from high to low address.

Exercise 7.5: 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.

Hints:

  1.  Examine the bit patterns of (1 << j) for j=0 to 7.
  2.  If char c is a byte, the C statement

if ( c & (1<<j) )     (j=0 to 7)

tests whether bit j of c is 1 or 0.

The outputs of the modified program should look like Fig. 7.6, in which each char represents a bit in the imap.

Exercise 7.6: 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, creation time, hard link count and data block numbers, etc. The program in Example 7.3 displays the INODE information of the root directory of an EXT2 file system.

Example 7.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”; // default to 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) < 0);

if (fd < 0){

printf(“open failed\n”); exit(1);

}

get_block(fd, 1, buf);                // get superblock

sp = (SUPER *)buf;

firstdata = sp->s_first_data_block;

inodesize = sp->s_inode_size;

blksize = 1024*(1<<sp->s_log_block_size);

printf(“first_data_block=%d block_size=%d inodesize=%d\n”,

firstdata, blksize, inodesize);

get_block(fd, (firstdata+1), 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,

gp->bg_free_blocks_count);

printf(“free_blocks=%d free_inodes=%d\n”,

gp->bg_free_inodes_count,

gp->bg_used_dirs_count);

iblock = gp->bg_inode_table;

printf(“root inode information:\n”, iblock);

printf(“——————— \n”);

get_block(fd, iblock, buf);

ip = (INODE *)buf +1;              //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 7.7 shows the root inode of an EXT2 file system.

In Fig. 7.7, the 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 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

Thus, 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). 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;                 // in temp[ ]

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 7.7: 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 1KB 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. So we only need to go through at most 12 direct blocks. For an empty EXT2 file system, the program’s output should look like Fig. 7.8.

Exercise 7.8: Mount mydisk under Linux. Create new directories and copy files to it. Umount it. Then run the dir.c program on mydisk again to see the outputs, which should look like Fig. 7.9. The reader should have noticed 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 memory 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 7.9: Assume INODE is the struct ext2_inode type. Given an inode number, ino, write C statements to return a pointer to its INODE structure.

Hints:

  1.  int fd = Open vidsk for READ;
  2.  int offset = inodes_start_block * BLKSIZE + (ino-1)*inode_szie;
  3.  lseek fd to offset;
  4.  read inode_size bytes into an INODE inode;
  5.  return (INODE *ip = &inode);

Exercise 7.10: Given an INODE pointer to a DIRectory inode. Write a

int search(INODE *dir, char *name)

function which searches for a name string in the directory; return its inode number if found, return 0 if not.

Source: Wang K.C. (2018), Systems Programming in Unix/Linux, Springer; 1st ed. 2018 edition.

Leave a Reply

Your email address will not be published. Required fields are marked *