We read often that saving the very first sector of a disk (the Master Boot Record) is a good practice, useful in case of disaster recovery. Nobody tells you that saving only the MBR is not sufficient if your disk contains extended partitions, to save the layout of the extended partition(s) you must follow and save also the Extended Partition Pointers.
This perl script will display and save the disk MBR (Master Boot Record). If there are some Extended Partitions, each Extended Partition Pointer will be analyzed and the pointed partition tables will be saved too.
The script is intended for a Linux system, it uses the dd
command to save the relevant disk sectors. The commands required to eventually restore the partitions, are displayed during the backup.
#!/usr/bin/perl #------------------------------------------------------------------------- # Display existing primary and extended partitions. # Backup the MBR and all of the EPPs sectors. # # Version 1.0 2005-11-08 # # Author Niccolo Rigacci <niccolo@rigacci.org> #------------------------------------------------------------------------- $ENV{PATH} = '/usr/local/bin:/usr/bin:/bin'; use strict; my $bs = 512; my $epp_num = 0; my $lpart = 0; my $dev; my $prefix; my $cmd; my $i; my $fp; my $sector; my $offset; my $bootable; my $type; my $start_lba; my $size; my $primary_extended_offset; my $NAME = `basename $0`; chomp($NAME); my %p_type = ( 0x05 => 'Extended', 0x06 => 'FAT16', 0x0b => 'Win95 FAT32', 0x0c => 'Win95 FAT32 (LBA)', 0x0e => 'Win95 FAT16 (LBA)', 0x0f => 'Win95 Ext\'d (LBA)', 0x82 => 'Linux swap', 0x83 => 'Linux', 0xfd => 'Linux raid autodetect', ); #------------------------------------------------------------------------- # Check command line. #------------------------------------------------------------------------- $dev = $ARGV[0]; $prefix = $ARGV[1]; if ($dev eq '' or $prefix eq '') { print "Usage: $NAME <dev> <save_prefix>\n"; exit(1); } if (! -b "/dev/$dev") { print "$NAME: Not a block device: /dev/${dev}\n"; exit(1); } #------------------------------------------------------------------------- # Read the MBR. #------------------------------------------------------------------------- print "--------------------------------------------------\n"; print "MBR of disk ${dev}\n"; print "--------------------------------------------------\n"; $cmd = "dd if=/dev/${dev} of=\"${prefix}${dev}.mbr\" bs=${bs} count=1"; print "Now saving with: $cmd\n"; `$cmd`; die("$NAME: dd failed. Stopped") if ($? != 0); print "Eventually restore with: dd if=\"${dev}.mbr\" of=/dev/$dev bs=${bs} count=1\n"; open($fp, "${prefix}${dev}.mbr") or die; die if (read($fp, $sector, $bs) != 512); close($fp); for ($i = 0; $i < 4; $i++) { $offset = 0x1be + 16 * $i; $bootable = &byte_value($sector, $offset + 0); $type = &byte_value($sector, $offset + 4); $start_lba = &dword_value($sector, $offset + 8); $size = &dword_value($sector, $offset + 12); if ($type != 0) { print("\n"); printf("Primary partition %d (/dev/%s%d)\n", $i, $dev, $i + 1); printf("\tbootable %02x\n", $bootable); printf("\ttype %02x (%s)\n", $type, $p_type{$type}); printf("\tStart LBA %d\n", $start_lba); printf("\tSectors %d\n", $size); if ($type == 0x05 or $type == 0x0f) { print "\tFollowing the Extended Partitions chain...\n"; $primary_extended_offset = $start_lba; &visit_epp($start_lba); } } } #------------------------------------------------------------------------- # Analyze an Extended Partition Pointer. #------------------------------------------------------------------------- sub visit_epp { my $start = shift; my $cmd; my $fp; my $sector; my $i; my $offset; my $bootable; my $type; my $start_lba; my $size; my $next_epp; $epp_num++; die("$NAME: Too many EPPs, loop bug?. Stopped") if ($epp_num > 100); print "\n"; print "--------------------------------------------------\n"; print "Extended Partition Pointer # ${epp_num} at ${start}\n"; print "--------------------------------------------------\n"; $cmd = "dd if=/dev/${dev} of=\"${prefix}${dev}.epp.${start}\" bs=${bs} count=1 skip=${start}"; print "Now saving with: $cmd\n"; `$cmd`; die("$NAME: dd failed. Stopped") if ($? != 0); print "Eventually restore with: dd if=\"${dev}.epp.${start}\" of=/dev/${dev} bs=${bs} count=1 seek=${start}\n"; open($fp, "${prefix}${dev}.epp.${start}") or die; die if (read($fp, $sector, $bs) != 512); close($fp); for ($i = 0; $i < 4; $i++) { $offset = 0x1be + 16 * $i; $bootable = &byte_value($sector, $offset + 0); $type = &byte_value($sector, $offset + 4); $start_lba = &dword_value($sector, $offset + 8); $size = &dword_value($sector, $offset + 12); if ($type != 0) { print("\n"); if ($type != 0x5) { $lpart++; printf("Logical partition %d (/dev/%s%d)\n", $i, $dev, 4 + $lpart); } else { printf("Secondary extended partition\n"); } printf("\tbootable %02x\n", $bootable); printf("\ttype %02x (%s)\n", $type, $p_type{$type}); printf("\tStart LBA %d\n", $start_lba); printf("\tSectors %d\n", $size); if ($type == 0x5) { $next_epp = $primary_extended_offset + $start_lba; &visit_epp($next_epp); } } } } #------------------------------------------------------------------------- # Get the byte value at the specified $offset from $buffer. #------------------------------------------------------------------------- sub byte_value { my $buffer = shift; my $offset = shift; return(ord(substr($buffer, $offset, 1))); } #------------------------------------------------------------------------- # Convert a 4 bytes string to a Double Word integer. #------------------------------------------------------------------------- sub dword_value { my $buffer = shift; my $offset = shift; my $b0 = ord(substr($buffer, $offset + 0, 1)); my $b1 = ord(substr($buffer, $offset + 1, 1)); my $b2 = ord(substr($buffer, $offset + 2, 1)); my $b3 = ord(substr($buffer, $offset + 3, 1)); return($b0 + $b1 * 256 + $b2 * 65536 + $b3 * 16777216); }