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 grub-install.

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-install assumes 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.)
  • grub-install uses df to 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 /dev/mapper/hda.

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):

  1. Loop mount your partitioned disk image:
    mathias:~ evan$ sudo losetup --show --find disk.img
    /dev/loop0
  2. 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
  3. Create a device-mapper node. Any name of the form hd[a-z], sd[a-z], or 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 /dev/mapper:
    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
    
  4. Use kpartx to 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
  5. 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
  6. 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
  7. 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)

  8. 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
  9. 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 update-grub).

9 Responses to “Installing GRUB onto a Disk Image”

  1. Wow, what a timely post. I’ve always had a challenge on this one. Thanks so much!

    FWIW, the horizontal scrollers, especially indented by the list, really take away from the readability of the content. Bless $DIETY for Firebug and the ability to reformat everything so I can ingest it all!

  2. Yeah, I don’t like the scrollbars either, but it seemed more readable than either having the text flow off the side of the content bar or having things wrap.

    How would you like to see it formatted?

  3. Hi

    I tried to find a way to install grub2 on partitioned image file quite long. I was happy to see that you were successfull and hoped that it will work on my system too. Unfortunately it doesn’t…

    I’m using Debian Lenny (Stable) and tried your procedure (and much more) with grub2 from Debian Squeeze (Testing) and Debian sid (Unstable) –> Everything was from stable release. I only used grub2 package from newer Debian releases.

    If it’s really working on your system it might have something to do with grub2 version you are using… do you have further information about which grub2 version you are using?

    There is something which is confusing me… On http://savannah.gnu.org/bugs/?27737 it seems that what we try to do is not yet possible and a known bug.

  4. Like I said in the post,

    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.

  5. Thanks a lot for this post. It was extremely useful.

    For people who are following the steps outlined above two years after publishing the post, be aware of two new hurdles:

    - Nowadays that there are two versions of grub, make sure to use the same version of grub-install and the grub that you install in the guest file system.
    - grub-install didn’t work out as straight forward as described in this post. I speculate – though I haven’t verified – that grub-install have been updated in a way that it now resolves symbolic links. “/dev/mapper/hda” will be translated into some “/dev/dm-X”. When the latter is searched for in the “device.map” it is simply not found. You guys still interested in this post might need to hack the grub-install to disable resolving symbolic links.

    Thanks again!

  6. Yes, you’re correct. I haven’t updated these instructions for GRUB 2, which is substantially different from GRUB Legacy, so they likely won’t work.

  7. hi,
    Thanks a million!
    I just followed your instruction and succeeded on an redhat with grub 0.97

Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2013 No Name Blog Suffusion theme by Sayontan Sinha