As part of my summer internship, I needed to write an installer for VMs. For various reasons, I wasn’t able to use the multitude of VM installers already out there, but one thing I noticed is that most of them don’t actually install a bootloader. They create a
/boot/grub/menu.lst, but never run
Turns out this is because it’s hard to do.
grub-install is very complicated and seems to be pretty explicitly designed for the case of running in an installer environment, where all of the disks and block devices are laid out the same way as they will be the next time you boot. When you’re installing in a host into a loop mount or something, that’s definitely not the case.
In trying to make this work, I discovered a few core issues:
grub-installassumes that the block device you’re installing onto “looks like” the sort of device you’d normally install GRUB onto (i.e. is named like a hard disk or floppy – hda, sda, fd0, etc.)
dfto determine the block device a given file or directory’s filesystem is on. That works really poorly when you’re already chrooting into your loop mount.
If you read my wording carefully, you might see where I’m going with this. In order to get grub-install to work, I needed to convince it it’s installing onto a hard drive, and I needed to run it outside of the loop mount.
The former is obviously a bit more challenging, and to accomplish that, I used the device-mapper to create a node named something like
I’ve only tested this on an Ubuntu Jaunty host so far, so I can’t guarantee that it works on Debian or even other Ubuntu versions, but I think it should. I’d love to hear if you have good or bad experiences on other Linux versions.
Here’s roughly how it works (you’ve probably performed some of these steps already in the process of running an installer):
- Loop mount your partitioned disk image:
mathias:~ evan$ sudo losetup --show --find disk.img /dev/loop0
- To setup the device map, you’ll need the major and minor numbers of the loop device, and the size (in bytes) of the disk. The latter is easiest to get from the disk image file, instead of from the loop device (emphasis mine):
mathias:~ evan$ ls -l /dev/loop0 brw-rw---- 1 root disk 7, 0 2009-07-18 11:27 /dev/loop0 mathias:~ evan$ ls -l disk.img -rw-r--r-- 1 evan evan 10737418240 2009-08-04 15:28 disk.img
- Create a device-mapper node. Any name of the form
vd[a-z]will work. Others might as well. The size of the disk should be converted to 512-byte sectors, and the device numbers for the loop device should be in the form major:minor. This will create a new device node in
mathias:~ evan$ echo '0 20971520 linear 7:0 0' | sudo dmsetup create hda mathias:~ evan$ ls -l /dev/mapper/hda brw-rw---- 1 root disk 252, 4 2009-08-04 15:36 /dev/mapper/hda
kpartxto create device-mapper nodes for the partitions on the disk image:
mathias:~ evan$ sudo kpartx -a /dev/mapper/hda mathias:~ evan$ ls -l /dev/mapper/hda* brw-rw---- 1 root disk 252, 4 2009-08-04 15:36 /dev/mapper/hda brw-rw---- 1 root disk 252, 5 2009-08-04 15:38 /dev/mapper/hda1 brw-rw---- 1 root disk 252, 6 2009-08-04 15:38 /dev/mapper/hda2
- Mount the root partition onto a tempdir (note: this is not a loop mount, because the kernel already thinks this is a real block device):
mathias:~ evan$ mktemp -d /tmp/tmp.MPUXeJWqpn mathias:~ evan$ sudo mount /dev/mapper/hda1 /tmp/tmp.MPUXeJWqpn
- Create a fake device.map for grub-install to use (yeah, this is a bad use of
tee, but I’m trying to be clear about what I’m doing):
mathias:~ evan$ echo '(hd0) /dev/mapper/hda' | sudo tee /tmp/tmp.MPUXeJWqpn/boot/grub/device.map (hd0) /dev/mapper/hda
- And now, for the grand finale, actually install GRUB from outside the chroot:
mathias:~ evan$ sudo grub-install --root-directory=/tmp/tmp.MPUXeJWqpn /dev/mapper/hda grub-probe: error: no mapping exists for `hda1' [: 494: =: unexpected operator Installing GRUB to /dev/mapper/hda as (hd0)... Installation finished. No error reported. This is the contents of the device map /tmp/tmp.MPUXeJWqpn/boot/grub/device.map. Check if this is correct or not. If any of the lines is incorrect, fix it and re-run the script `grub-install'. (hd0) /dev/mapper/hda
(You don’t need to worry about those two errors at the beginning of the output – it’s some logic specialized for XFS filesystems)
- Cleanup the mess you made:
mathias:~ evan$ sudo umount /tmp/tmp.MPUXeJWqpn mathias:~ evan$ sudo rm -rf /tmp/tmp.MPUXeJWqpn mathias:~ evan$ sudo kpartx -d /dev/mapper/hda mathias:~ evan$ sudo dmsetup remove hda mathias:~ evan$ sudo losetup -d /dev/loop0
- Finally, examine your disk image, and see that it definitely has GRUB installed:
mathias:~ evan$ file disk.img disk.img: x86 boot sector; GRand Unified Bootloader, stage1 version 0x3, 1st sector stage2 0x884009; partition 1: ID=0x83, active, starthead 0, startsector 1, 18876374 sectors; partition 2: ID=0x82, starthead 254, startsector 18876375, 2088450 sectors
And there you have it! You will, of course, still need to write out GRUB’s menu.lst through some other means (such as Debian/Ubuntu’s