CSCI 372 System Administration — Finding a file

Getting an image

The /boot file system of a Raspberry Pi is formatted as a FAT16 file system. It’s a small file system so we’re just going to make a copy of it. This is the only time, we’ll need to use the sudo command.

[…]$ cd
[…]$ sudo dd if=/dev/mmcblk0p1 of=boot.img bs=1M

We store the image of /boot into the file boot.img

The reference

Download a copy of the Microsoft EFI FAT32 File System Specification. It is the only reference we will use.

The specification is in MS Word format. You will have to accept a license agreement.

The BPB and its fields

Read pages 7 and 8 which talks about the BPB (BIOS Parameter Block), which is contained in the first 512 bytes of the file system image. Use the dd command to copy those first 512 bytes into a file called boot.BPB.img.

[…]$ dd if=boot.img of=boot.BPB.img bs=512 count=1
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.00834778 s, 61.3 kB/s

The BPB contains many field that specify the layout of the file system. We are going to get copies of the ones we will need in our task. We will use od to get these fields and assign them to shell variables. We are using the variable name of the Microsoft specification.

This is going to be tedious.

Bytes per sector

Sectors are the smallest unit of disk allocation.

[…]$ od -A d -j 11 -N 2 -t d2 boot.BPB.img 
0000011    512
0000013
[…]$ BPB_BytsPerSec=512

Sectors per cluster

However, files are allocated in clusters. Sectors are just too small for efficient file allocation.

[…]$ od -A d -j 13 -N 1 -t d1 boot.BPB.img 
0000013   16
0000014
[…]$ BPB_SecPerClus=16

Number of reserved sectors

This is the number of sectors reserved for BPB and, presumably, other stuff. The Microsoft specification states that it should “never be anything other than 1’ for a FAT16 file system. However, that doesn&rssquo;t seem to always be the case.

[…]$ od -A d -j 14 -N 2 -t d2 boot.BPB.img 
0000014     16
0000016
[…] $BPB_RsvdSecCnt=16

Number of FATs

The FAT file system is named after the File Allocation Table which tells you were the cluster of files and directories are stored. There ought to be only two of these.

[…]$ od -A d -j 16 -N 1 -t d1 boot.BPB.img 
0000016    2
0000017
[…]$ BPB_NumFATs=2

Root directory entry count

This is a count of the number of root directory entries for FAT12 and FAT16 file systems. Each of these entries is 32-bytes long. Expect to find 512 entries.

[…]$ od -A d -j 17 -N 2 -t d2 boot.BPB.img 
0000017    512
0000019
[…]$ BPB_RootEntCnt=512

Total sectors

This one can be tricky because the number of sectors may by larger than the maximum number (65535 or 216-1) that can be stored in 16 bits. If the 16 bit field is 0, you must look at the 32 bit field.

[…]$ od -A d -j 19 -N 2 -t d2 boot.BPB.img 
0000019      0
0000021
[…]$ BPB_TotSec16=0
[…]$ od -A d -j 32 -N 4 -t d4 boot.BPB.img 
0000032      114688
0000036
[…]$ BPB_TotSec32=114688
[…]$ TotSec=$BPB_TotSec32
[…]$ echo $TotSec
114688

Sanity check

If you multipy the number of sectors by the number of bytes per sectors, you will find out the size of the file system. The ought to be the same size as the file system image. It certainly can’t be bigger.

[…]$ echo $(( $TotSec * $BPB_BytsPerSec ))
58720256
[…]$ ls -l boot.img
-rw-r--r-- 1 root root 58720256 Mar 24 09:17 boot.img

That one looks good.

What kind of FAT

We’ve made it to pages 13 &d 14 of the specification where we can take our first look at the File Allocation Table or FAT.

However, we better first figure out there the data sectors really are located. That requires knowing the sizes of the root directory (for FAT16) and FAT structures.

Number of root directory sectors

In a FAT12 or FAT16 file system, a number of sectors are allocated for the root directory.

[…]$ RootDirSectors=$(( ( ( $BPB_RootEntCnt * 32 ) + ( $BPB_BytsPerSec - 1 ) ) / $BPB_BytsPerSec ))
[…]$ echo $RootDirSectors
32

If you perform this calculation for a FAT32 system, it will always assign 0 because BPB_RootEnCnt is 0.

Number of FAT sectors

We have to read some more BPB fields to determine the number of sectors used to store a single copy of the FAT.

[…]$ od -A d -j 22 -N 2 -t d2 boot.BPB.img 
0000022     32
0000024
[…]$ BPB_FATSz16=32
[…]$ FATSz=$BPB_FATSz16

First data cluster

The first data cluster follows the reserved sectors, the FAT sectors and the root directory sectors (for FAT16). Determine the first data sector and the number of data sectors.

[…]$ FirstDataSector=$(( $BPB_RsvdSecCnt + ( $BPB_NumFATs * $FATSz ) + $RootDirSectors ))
[…]$ echo $FirstDataSector
112
[…]$ DataSec=$(( $TotSec - ( $BPB_RsvdSecCnt + ( $BPB_NumFATs * $FATSz ) + $RootDirSectors ) ))
[…]$ echo $DataSec
114576

What FAT

The official MS-sanctioned way to determine FAT type is to calculate the number of sectors and compare that number to 212, 216 and 232.

[…]$ CountofClusters=$(( $DataSec / $BPB_SecPerClus ))
[…]$ echo $CountofClusters
7161

This is officially a FAT16 file system.

Root directory

Finally, let’s copy the root directory.

[…]$ echo $(( $BPB_RsvdSecCnt + $BPB_NumFATs * $FATSz ))
80
[…]$ echo $RootDirSectors
32
[…]$ dd if=boot.img of=boot.rootdir.img bs=512 count=32 skip=80
32+0 records in
32+0 records out
16384 bytes (16 kB) copied, 0.00549986 s, 3.0 MB/s

Starting at page 22, the directory structure is explained in the Microsoft specification.

First directory entry

The first directory entry is for the directory itself.

[…]$ od -A d -j 0 -N 32 -t a boot.rootdir.img
0000000   b   o   o   t  sp  sp  sp  sp  sp  sp  sp  bs nul nul   U   &
0000016   '   D   '   D nul nul   U   &   '   D nul nul nul nul nul nul
0000032

Second directory entry

The second directory entry is a long entry for the lower-case name bootcode.bin . We will ignore it.

[…]$ od -A d -j 32 -N 32 -t a boot.rootdir.img
0000032   A   b nul   o nul   o nul   t nul   c nul  si nul enq   o nul
0000048   d nul   e nul   . nul   b nul   i nul nul nul   n nul nul nul
0000064

The third directory entry

Finally, we get the real directory entry, the one for BOOTCODE.BIN .

[…]$ od -A d -j 64 -N 32 -t a boot.rootdir.img
0000064   B   O   O   T   C   O   D   E   B   I   N  sp nul   d   T   %
0000080   9   E   9   E nul nul   T   %   9   E etx nul   0   E nul nul
0000096

Get the size of BOOTCODE.BIN .

[…]$ od -A d -j $(( 64 + 28 )) -N 4 -t d4 boot.rootdir.img
0000092       17840
0000096

Make sure it matches the size of BOOTCODE.BIN as seen by the operating system.

[…]$ ls -l /boot/bootcode.bin 
-rwxr-xr-x 1 root root 17840 Sep 25  2014 /boot/bootcode.bin

Now determine the address of the first cluster of BOOTCODE.BIN .

[…]$ od -A d -j $(( 64 + 26 )) -N 2 -t d2 boot.rootdir.img
0000090      3
0000092
[…]$ BOOTCODEBINCLUSTER=3

The FAT

Copy the entire FAT.

[…]$ dd if=boot.img of=boot.fat.img bs=512 skip=16 count=32
32+0 records in
32+0 records out
16384 bytes (16 kB) copied, 0.0144706 s, 1.1 MB/s

Take a quick look at the FAT’s linked list structure. Our file seems to be in clusters 3, 4 and 5.

[…]$ od -A d -j 0 -N 32 -t d2 boot.fat.img 
0000000     -8     -1      0      4      5     -1     -1     -1
0000016     -1     10     -1     12     13     14     15     16
0000032

Do a quick check to determine that the file contains three clusters.

[…]$ echo $*(( $BPB_SecPerClus * $BPB_BytsPerSec ))
(( 16 * 512 ))
[…]$ echo $(( $BPB_SecPerClus * $BPB_BytsPerSec ))
8192
[…]$ echo $(( ( 17840 - 1) / 8192 + 1 ))
3

The file

Finally, let’s read the file from the raw disk image.

Start by one last look at the relevant fields of the FAT.

[…]$ od -A d -j 6 -N 6 -t d2 boot.fat.img 
0000006      4      5     -1
0000012

Get the sector address of the three clusters.

[…]$ FirstSectorofCluster3=$(((  ( 3 - 2 ) * $BPB_SecPerClus ) + $FirstDataSector ))
[…]$ FirstSectorofCluster4=$(((  ( 4 - 2 ) * $BPB_SecPerClus ) + $FirstDataSector ))
[…]$ FirstSectorofCluster5=$(((  ( 5 - 2 ) * $BPB_SecPerClus ) + $FirstDataSector ))
[…]$ echo $FirstSectorofCluster3
128
[…]$ echo $FirstSectorofCluster4
144
[…]$ echo $FirstSectorofCluster5
160

Now read the three clusters.

[…]$ dd if=boot.img of=boot.cluster3.img bs=512 skip=128 count=16
16+0 records in
16+0 records out
8192 bytes (8.2 kB) copied, 0.00104097 s, 7.9 MB/s
[…]$ dd if=boot.img of=boot.cluster4.img bs=512 skip=144 count=16
16+0 records in
16+0 records out
8192 bytes (8.2 kB) copied, 0.00104697 s, 7.8 MB/s
[…]$ dd if=boot.img of=boot.cluster5.img bs=512 skip=160 count=16
16+0 records in
16+0 records out
8192 bytes (8.2 kB) copied, 0.00118497 s, 6.9 MB/s

Concatentate the three clusters and truncate the result to 17840 bytes.

[…]$ cat boot.cluster3.img boot.cluster4.img boot.cluster5.img > boot.clusters3-5.img
[…]$ ls -l boot.cluster*
-rw-r--r-- 1 brock brock  8192 Apr  7 00:02 boot.cluster3.img
-rw-r--r-- 1 brock brock  8192 Apr  7 00:02 boot.cluster4.img
-rw-r--r-- 1 brock brock  8192 Apr  7 00:02 boot.cluster5.img
-rw-r--r-- 1 brock brock 24576 Apr  7 00:03 boot.clusters3-5.img
[…]$ dd if=boot.clusters3-5.img of=boot.bootcode.bin.img bs=17840 count=1
1+0 records in
1+0 records out
17840 bytes (18 kB) copied, 0.0110697 s, 1.6 MB/s

This better work.

[…]$ ls -l boot.bootcode.bin.img 
-rw-r--r-- 1 brock brock 17840 Apr  7 00:04 boot.bootcode.bin.img
[…]$ ls -l /boot/bootcode.bin 
-rwxr-xr-x 1 root root 17840 Sep 25  2014 /boot/bootcode.bin
[…]$ cmp boot.bootcode.bin.img /boot/bootcode.bin 
[…]$ echo $?
0

It does!