Creating partitioned virtual disk images

Warning: This blogpost has been posted over two years ago. That is a long time in development-world! The story here may not be relevant, complete or secure. Code might not be complete or obsoleted, and even my current vision might have (completely) changed on the subject. So please do read further, but use it with caution.
Posted on 11 Oct 2011
Tagged with: [ cybos ]  [ ext2fs ]  [ losetup ]  [ mount

Using a disk image is very easy: download the file, mount it through a so-called “loopback” device and your OS will see the image as it was an real harddisk, CDR or DVD. When I needed to test the IDE-drivers, the partitioning-functionality, the ext2 drivers etc, I wanted to use just such an image so I can quickly make modifications and check how the actual structure looked like, just by reading the disk image file. This is a great help when it comes to debugging.

Our OS debugging takes place in both QEMU and Bochs. These two virtual machines / emulators can use a disk image to emulate a harddisk. This way, I can test my IDE/ATA drivers all the way up to the virtual file system to see if everything works.

  1. Create an empty image.
  2. Partition the image through fdisk
  3. Create the loopbacks for each partition
  4. Format the partitions
  5. Mount the partitions and populate

Create an empty image.

There are multiple ways to do this, but one of the easiest methods is to use either dd or bximage. The latter one is a tool from the Bochs Emulator that can create disks.

0xdeadbeef:image joshua$ <strong>bximage</strong>
========================================================================
                                bximage
                  Disk Image Creation Tool for Bochs
        $Id: bximage.c,v 1.34 2009/04/14 09:45:22 sshwarts Exp $
========================================================================

Do you want to create a floppy disk image or a hard disk image?
Please type hd or fd. [hd] 

What kind of image should I create?
Please type flat, sparse or growing. [flat] 

Enter the hard disk size in megabytes, between 1 and 129023
[10] 

I will create a 'flat' hard disk image with
  cyl=20
  heads=16
  sectors per track=63
  total sectors=20160
  total size=9.84 megabytes

What should I name the image?
[c.img] <strong>hdd.img</strong>

Writing: [] Done.

I wrote 10321920 bytes to hdd.img.

The following line should appear in your bochsrc:
    ata0-master: type=disk, path="hdd.img", mode=flat, cylinders=20, heads=16, spt=63

Another way is to use the “dd” utility that comes on most unix/linux systems:

dd if=/dev/zero of=hdd.img bs=1024 count=10240

You will notice a difference in size even though I both entered that I wanted 10MB of space. This is because bximage does it’s calculations on CHS level, while dd just copy bytes. For our purpose, this doesn’t matter.

Partition the image through fdisk

Now that we have a completely blank file (or virtual disk), we need to partition it just like any other disk. The “fdisk” utility it the way to go here, and there is nothing different between creating partitions on a virtual disk than it is on a physical one.

fdisk hdd.img

I’ve added a bunch of partitions (again: I’m testing my OS here, so it needs to be able to handle stuff like extended partitions etc). Just make sure you set the correct settings for your cylinders, heads and sectors-per-track in the fdisk menu. They are located in the “Expert menu” through “x”. I’ve added a big 5MB partition for Linux, a small 2MB partition for dos/fat16, another 1.5MB for linux, and a-little-bit-less-than-1MB partition for Linux, but I’ve put the last one inside an extended partition:

# fdisk -l -u hdd.img -C 20 -H 16 -S 63

Disk hdd1.img: 0 MB, 0 bytes
16 heads, 63 sectors/track, 20 cylinders, total 0 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: 0x839d4362

   Device Boot      Start         End      Blocks   Id  System
hdd.img1              63       10079        5008+  83  Linux
hdd.img2           10080       15119        2520    6  FAT16
hdd.img3           15120       18143        1512   83  Linux
hdd.img4           18144       20159        1008    5  Extended
hdd.img5           18207       20159         976+  83  Linux

Now for the most important part, we NEED to remember the values used in the “Start” and “End” column. As you can see, the first partition does not start at 0 as you might have expected. That is because fdisk leaves room for boot-programs in this first part of the disk. The actual partition starts on sector 63, which is 63 * 512 = position 32256 inside our virtual image. This is important to realize in the next part..

Create the loopbacks for each partition

Get your calculator up and running, because we need a bit of math to do. This is because we don’t mount the whole file as a whole, but we mount each partition separately. So we get 4 mounts: primary partition 1 (linux), primary partition 2 (fat16), primary partition 3 (linux) and extended partition 5 (linux). We don’t need to mount the extended partition, since this is not really more than a placeholder for other partitions.

Before we can mount the partitions, we add them to the loopback driver. This is done with “losetup”.

# losetup -o 32256 --sizelimit 5160448 /dev/loop1 hdd.img

We have 4 arguments: the start (offset) inside the file, the LIMIT of the image, the loopback device we want to add the file to, and the last one is the actual file that we want to create a loopback for.

The offset is a byte-offset and is calculated by \* 512. As you saw in the fdisk-list above, partition 1 starts at sector 63, so 63 \* 512 makes 32256. The sizelimit is **VERY** important. It tells the loopback driver it should not use the complete file, but only until this limit. It is calculated by the end-sector of the partition. In our case: 10079 * 512 = 5160448.

The complete list  should look like this:

# losetup -o 32256 --sizelimit 5160448 /dev/loop1 hdd.img
# losetup -o 5160960 --sizelimit 7740928 /dev/loop2 hdd.img
# losetup -o 7741440 --sizelimit 9289216 /dev/loop3 hdd.img
# losetup -o 9321984 --sizelimit 10321408 /dev/loop5 hdd.img

I’ve used the /dev/loopN to match the actual partition. You could use /dev/loop0 to /dev/loop3 if you want. Doesn’t really matter.

Format the partitions

At this point you are able to format the partitions. Again, just like any physical partition.

# mkfs.ext2 /dev/loop1
# mkdosfs /dev/loop2
# mkfs.ext2 /dev/loop3
# mkfs.ext2 /dev/loop5

Did I mention the importance of the sizelimit argument during losetup? If you didn’t you will run into trouble when formatting the partitions. In the case of the first partition, the loopback device knows it starts from sector 63 (which is correct), but since you didn’t specify a limit, it thinks it can use the rest of the file. In our case, this would mean it would use a little bit less than 10MB. Same goes for the other partitions as well.

Mount the partitions and populate

Now we can actually mount and populate the partitions.

mkdir /mnt/hd1p1 ; mount /dev/loop1 /mnt/hd1p1
mkdir /mnt/hd1p2 ; mount /dev/loop2 /mnt/hd1p2
mkdir /mnt/hd1p3 ; mount /dev/loop3 /mnt/hd1p3
mkdir /mnt/hd1p5 ; mount /dev/loop5 /mnt/hd1p5

Now you can populate the filesystems any way you like (and in my case, I can actually mount them correctly from CybOS :))

Conclusion

Using partitioned based virtual images isn’t something that is used much, but can come in handy from time to time. One of the biggest issues with them is not to forget the “-sizelimit” argument during losetup. Forgetting this can make your life miserable since you can (and will) overwrite data from other partitions as well without noticing.