Entries‎ > ‎

Automatically creating a disk image with partitions and bootloader.

I'm often playing with tools to manipulate full system images for virtual machines and so I often need to create disk images.

The attached script allows to create a disk image of an arbitrary size with partitions and a working grub bootloader. This kind of script can be a bit dangerous, so I put it there just as an example, be careful. It is using qemu-img to create the disk image (which can be easily replaced by dd), sfdisk to create the partitions, and grub to install a boot loader.

There is behind that a couple of not often documented issues.

Disk size

The PC partition table still uses the wonderful Cylinder/Head/Sector (a.k.a. CHS) scheme to address the disk. Of course, those values do not correspond anymore to any physical reality, but they are still here to annoy you. The idea with PC partition table is that partitions can start and stop only at CHS boundaries. Typically, you count 255 heads and 63 sectors, and you have a number of cylinders depending on the disk size. Usually one sector is 512 octets, so the math is easily done.

It is not a problem to have a disk size not strictly aligned with a CHS boundary. Worst thing is that you lose a few kilobytes, which is not an issue. However when it comes to creating a disk image from an host, you can have the following problem:

  • You create the image:
qemu-img create -f raw "${IMGFILE}" ${IMGSIZE}
  • Then you create the partition table. I'm using here option -D for sfdisk. It will move the first partition a bit forward (one head further). In practice, I do that to match the usual partition scheme of tools such as fdisk. The following command creates only one linux partition, taking the whole disk and marks it as bootable.
sfdisk -D $IMGFILE <<EOF ,,L,* ; ; ; EOF
  • And then, you want to format the partition. The partition is not starting at the beginning of the file, so you first need to do some trickery using linux loopback devices. The offset 32256 come from 63 * 512, with 63 being the number of sectors and 512 the size of one sector.
losetup -o 32256 /dev/loop0 "${IMGFILE}"
  • Now, you have a block device, /dev/loop0, which corresponds to the partition, so you can format it. Be careful, there's no confirmation here :
mkfs.ext3 /dev/loop0

And now you have a problem if you look carefully. The mkfs called will determine (by default) the filesystem size based on the block device size. But if your disk size was not aligned on CHS boundaries, it means that you have more place on the disk image, hence on/dev/loop0 than you really have on the partition. Another way to say that is that the end of the filesystem will be after the end of the partition. And of course, fsck won't be too thrilled about that.

There's a couple of solutions:

  • Explicitely set number of blocks on the mkfs command. E.g, mkfs.ext3 -b 4096 /dev/loop0 <number of block>. You can compute number of blocks from the disk geometry.
  • Set the size of the disk to perfectly fit with the geometry, so you don't have to teach mkfsabout size. That the approach I'm doing in the script, because sfdisk is a pain to parse to obtain the geometry.

Configuring grub

Grub can be easily put a disk image. In its default setup, it needs to have a few files on the partition to be able to boot and show up a menu. At the beginning, I was simply counting on the files provided by the linux distribution I was putting on the disk. However, it can be sometime incompatible with the grub version you're using from the host machine. So you need to put grub files from the host machine first. I'm doing something along those lines after having mounted the partition on $INSTDIR:

cp /boot/grub/{stage1,stage2,e2fs_stage1_5} $INSTDIR/local/grub ln -s /boot/grub/menu.lst $INSTDIR/local/grub/menu.lst

I'm not putting the host file in the usual /boot/grub directory. The idea is that I'm going to put a full distribution image here, and I don't want to override the real grub files. Moreover, this way, if I reinstall grub from the virtual machine afterwards, it will probably do the right thing by using files from /boot/grub.

Now, I just need to setup grub using the following commands:

grub --batch <<EOF device (hd0) ${IMGFILE} root (hd0,0) setup --prefix=/local/grub (hd0) quit EOF

There are two things here. I need to specify that hd0 is in fact my disk image using thedevice stanza, to avoid writing on the real disk. Then, I use --prefix in the setup command to make sure that grub will be using the files I copied from the host.

Pierre Palatin,
Aug 12, 2011, 1:25 PM