Recovering from a Broken Partition Table

Finding Magic Numbers

First of all, if you know what the original values you had plugged in to fdisk were, just put things back the way they were and it should “just work”. That’s why it’s always a good idea toprint out your partition table before changing it. That way the numbers are there in the scrollback buffer.

[root]# fdisk /dev/sda

Command (m for help): p

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048     3905535     1951744   83  Linux
/dev/sda2         3905536   500117503   248105984   83  Linux

Now when it all hits the fan, you can just type in the numbers you see to get things back to normal. But let’s assume that’s not an option here: we need to actually find the filesystem on the disk.

Filesystems typically have a “magic number” that appears at a specific offset; find that magic number, and you often have found your filesystem. For Linux EXT-2/3/4, that magic number is 0xEF53 found at offset 1080 (0×0438).

An Example

So we have a disk at /dev/sdd. There’s no partition table. We know that it was formatted as ext4. We tried a few common possibilities in fdisk but to no avail. So let’s just grep the drive and look for our magic number.

Note that the EXT family of filesystems use Intel byte ordering, so the bytes appear in reverse order. So we’re looking for “53EF” instead of “EF53″. We’ll use dd to read the disk and xxd to hex-dump the data. In a sensible system the bytes should be aligned, but we can’t guarantee that. So most likely we’ll see something like:

.... .... 53ef .... .... 

but we can’t rule out something like this:

.... ..53 ef.. .... .... 

There’s the possibility of the byte sequence straddling a line-break in our display, but that’s harder to search for, so we’ll start with this:

[root]# dd if=/dev/sdd bs=1M count=10 skip=0 | xxd | grep -E '53 ?ef'

0053ef0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0153ef0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0200430: 49ad e052 0200 ffff 53ef 0100 0100 0000  I..R....S.......
0253ef0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0353ef0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

If you’re playing along at home, here’s what those options mean. if means “input file” — our disk in this case. bs means “block size”, which sets the context of the next options, and which we set to 1MB, count is the number of block to actually read (so that’s 10MB in our case), andskip tells how many blocks to skip (none). To grep we pass the -E option, meaning we’ll be using a real regular expression, while 53 ?ef means match “53″ possibly followed by an optional space, followed then by ef. We know that the hex output of xxd is lowercase, so we don’t need to do case-insensitive matching.

In the output, we see several lines where 53ef shows up in the address, while the first place where it shows up in the data looks like a reasonable candidate for our magic number. In the filesystem superblock, the pair of bytes right after the magic number is the filesystem state.0100 in this case would mean “cleanly unmounted”, so that’s reasonable. The pair right before it is the number of mounts allowed between checks; ffff means “no limit”, which sounds right. And the pair before that is the number of times the filesystem has been mounted; 0200 would mean 2 (Intel byte order). Again, looks reasonable. So let’s proceed.

The magic number was found on the line labeled 0×200430, with the sequence found 8 bytes in from there, so comes out to offset 0×200438. We were expecting the sequence to appear at offset 0×438, Subtracting 0×000438 from 0×200438 gives us the offset for the filesystem: 0×200000. That’s a pretty round number in hex, but in decimal it’s 2097152.

Now we have two options; we can either create a corresponding partition, or we can simply mount the filesystem at an offset. We’ll try the offset first as a test, and then create the partition table. You always want to mount filesystem read-only if you’re doing recovery work, hence thero option.

[root]# mount /dev/sdd -o ro,offset=2097152 /mnt/test/
[root]# ls /mnt/test/
.  ..  lost+found  my_important_stuff.txt
[root]# umount /mnt/test/

Looks like we have our filesystem back. Let’s create the partition table.

[root]# fdisk /dev/sdd

Command (m for help): p

Disk /dev/sdd: 8103 MB, 8103395328 bytes
250 heads, 62 sectors/track, 1021 cylinders, total 15826944 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x27b954b9

   Device Boot      Start         End      Blocks   Id  System

As mentioned, we always print out the partition table before changing it, just in case. But in this instance, we learn an important detail: all our numbers will be given in 512-byte sectors. If we divide our offset of 2097152 by 512, we get 4096. So that’s how many sectors in our partition needs to start.

Command (m for help): n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-15826943, default 2048): 4096
Last sector, +sectors or +size{K,M,G} (4096-15826943, default 15826943):
Using default value 15826943

Here we used the default “last sector”, assuming that the filesystem takes the rest of the partition. If it doesn’t, there’s no harm; the filesystem can be smaller than the partition that contains it, but you don’t want the partition to be too small.

Let’s print out our new partition table for our reference, and then save it to disk.

Command (m for help): p

Disk /dev/sdd: 8103 MB, 8103395328 bytes
250 heads, 62 sectors/track, 1021 cylinders, total 15826944 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x27b954b9

   Device Boot      Start         End      Blocks   Id  System
/dev/sdd1            4096    15826943     7911424   83  Linux

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.

Nothing left but to try it. Again, mounting read-only.

[root]#  mount -o ro /dev/sdd1 /mnt/test/
[root]#  ls /mnt/test/
.  ..  lost+found  my_important_stuff.txt

Success!

If you’re confident that you’ve restored things perfectly, you can remount read-write and go about your business. Otherwise, it’s a good idea to copy all the files off this drive and onto one that you’re certain has been properly partitioned and formatted.