As the virtualization is being spreaded into many field, learn to how manipulate virtual disk image is sometime you have to deal with. In Cisco CGR1000 and IR800 routers, there was a need to create a bootable disk image as a reference guest linux image for customer to use. Along the way, I found manipulating virtual disk file is actually very interesting.

Linux virtual disk

There are a few types of disk image, like ISO, raw disk image, vdi(VirtualBox), vmdk (VMWare) qcow2 (QEMU).

ISO image

An ISO image is an archive file of an optical disc. It can be burnt into CD/DVD. Often people will download ISO file like ubuntu distribution in ISO format, and burn into DVD/CD, and use it as an installation media.

You can also using linux tool “dd” to write ISO file into USB pendrive/memory stick, and use it as installation media. Assuing USB pendrive is detected by Linux as /dev/sdc, then following command will work:

weng@weng-u1604:~$ sudo if=ubuntu-16.04-desktop-amd64.iso of=/dev/sdc bs=10M; sync ; sync;sync

Additionaly, under linux, you can mount ISO file, inspect what is inside content, and you can make a copy and make modification (since iso is mounted as read only), and create a new ISO file. See example below.

[root@sg-centos-hv1 scratch]# 
[root@sg-centos-hv1 scratch]# losetup /dev/loop0 ubuntu-1504-mini.iso
[root@sg-centos-hv1 scratch]# ls /mnt/iso/
[root@sg-centos-hv1 scratch]# mount /dev/loop0 /mnt/iso
[root@sg-centos-hv1 scratch]# ls /mnt/iso
adtxt.cfg  exithelp.cfg  f2.txt  f5.txt  f8.txt     isolinux.bin  libcom32.c32  menu.cfg    splash.png   vesamenu.c32
boot       f10.txt       f3.txt  f6.txt  f9.txt     isolinux.cfg  libutil.c32   prompt.cfg  stdmenu.cfg
boot.cat   f1.txt        f4.txt  f7.txt  initrd.gz  ldlinux.c32   linux         rqtxt.cfg   txt.cfg
[root@sg-centos-hv1 scratch]# # the above two steps mount can be done with one step below.
[root@sg-centos-hv1 scratch]# umount /mnt/iso
[root@sg-centos-hv1 scratch]# mount -o loop ubuntu-1504-mini.iso /mnt/iso
[root@sg-centos-hv1 scratch]# ls /mnt/iso
adtxt.cfg  exithelp.cfg  f2.txt  f5.txt  f8.txt     isolinux.bin  libcom32.c32  menu.cfg    splash.png   vesamenu.c32
boot       f10.txt       f3.txt  f6.txt  f9.txt     isolinux.cfg  libutil.c32   prompt.cfg  stdmenu.cfg
boot.cat   f1.txt        f4.txt  f7.txt  initrd.gz  ldlinux.c32   linux         rqtxt.cfg   txt.cfg
[root@sg-centos-hv1 scratch]# # below show how to create a new ISO by adding new files  into existing ISO file
[root@sg-centos-hv1 scratch]# mkdir /scratch/new_iso
[root@sg-centos-hv1 scratch]# cp -rf /mnt/iso/*  /scratch/new_iso
[root@sg-centos-hv1 scratch]# touch /scratch/new_iso/hello.txt
[root@sg-centos-hv1 scratch]# ls /scratch/new_iso/
adtxt.cfg  exithelp.cfg  f2.txt  f5.txt  f8.txt     initrd.gz     ldlinux.c32   linux       rqtxt.cfg    txt.cfg
boot       f10.txt       f3.txt  f6.txt  f9.txt     isolinux.bin  libcom32.c32  menu.cfg    splash.png   vesamenu.c32
boot.cat   f1.txt        f4.txt  f7.txt  hello.txt  isolinux.cfg  libutil.c32   prompt.cfg  stdmenu.cfg
[root@sg-centos-hv1 scratch]# mkisofs -o /scratch/ubuntu-1504-mini-A.iso /scratch/new_iso/
I: -input-charset not specified, using utf-8 (detected in locale settings)
Using USBSE000.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/usbserial_pl2303.mod (usbserial_common.mod)
Using PART_000.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/part_sunpc.mod (part_sun.mod)
Using USBSE001.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/usbserial_common.mod (usbserial_ftdi.mod)
Using GFXTE000.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/gfxterm_background.mod (gfxterm_menu.mod)
Using VIDEO000.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/videotest_checksum.mod (videotest.mod)
Using PASSW000.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/password.mod (password_pbkdf2.mod)
Using MULTI000.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/multiboot.mod (multiboot2.mod)
Using GCRY_000.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/gcry_sha1.mod (gcry_sha256.mod)
Using USBSE002.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/usbserial_ftdi.mod (usbserial_usbdebug.mod)
Using MDRAI000.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/mdraid09.mod (mdraid09_be.mod)
Using GCRY_001.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/gcry_sha256.mod (gcry_sha512.mod)
Using XNU_U000.MOD;1 for  /scratch/new_iso/boot/grub/x86_64-efi/xnu_uuid.mod (xnu_uuid_test.mod)
 29.30% done, estimate finish Fri Aug 12 22:45:51 2016
 58.63% done, estimate finish Fri Aug 12 22:45:51 2016
 87.89% done, estimate finish Fri Aug 12 22:45:51 2016
Total translation table size: 0
Total rockridge attributes bytes: 0
Total directory bytes: 16384
Path table size(bytes): 50
Max brk space used 43000
17079 extents written (33 MB)
[root@sg-centos-hv1 scratch]# umount /mnt/iso
[root@sg-centos-hv1 scratch]# mount -o loop /scratch/ubuntu-1504-mini-A.iso /mnt/iso
[root@sg-centos-hv1 scratch]# ls /mnt/iso//hello.txt
/mnt/iso//hello.txt
[root@sg-centos-hv1 scratch]# umount /mnt/iso
[root@sg-centos-hv1 scratch]# 

Raw disk image: create a bootable raw disk image from scratch

There are a few steps to make a bootable raw disk image

  1. Using command "dd" to create a disk file.
  2. Setup the disk file as loopback block device.
  3. Create partition on the loopback block device.
  4. Create file system on the partition.
  5. Download and build bootloader.
  6. Build a kernel and root file system. in the example, kernel an root file system are built using yocto project.
  7. Install boot loader in the first partition, which is makred as bootable partition. below example it is grub as bootlader.
  8. Copy kernel and root file system into partition with file system setup.
  9. Test it using qemu
#!/bin/bash

################################################################################
# Description:
#     create a reference disk image file of Linux Guest OS
#
# Author: Wenwei Weng
#
# usage examples:
#
# ./mk_bootable_disk_img.sh -k bzImage.sda -r core-image-basic-qemux86.ext4 -b grub-0.97 -d sda -o linux-bootable-disk-sda.img
#
# ./mk_bootable_disk_img.sh -k bzImage.sda -r core-image-basic-qemux86.ext4 -b grub-0.97  -d hda -o linux-bootable-disk-hda.img
#
################################################################################

cleanmount() {
    umount loop2_mnt >& /dev/null
    umount ext4_mnt >& /dev/null
    rmdir loop2_mnt >& /dev/null
    rmdir ext4_mnt >& /dev/null
    umount /dev/loop2  >& /dev/null
    umount /dev/loop1   >& /dev/null
    losetup -d /dev/loop2  >& /dev/null
    losetup -d /dev/loop1  >& /dev/null
    sync
    sleep 2
}

#  default setting
# ROOTFS built from yocto
ROOTFSEXT4="core-image-basic-qemux86-64.ext4"
#Kernel image from yocto
BZIMAGE="bzImage"
# Grub legacy version
GRUBSRC="grub-0.97"
# Result image disk file
DISKIMG="linux-bootable-disk-sda.img"
# default image size 175MB
DISKIMG_SIZE=175

#default the disk is detected as /dev/hda
DEVDISK="hda"

#extra for application package
APP_PKG=""

#############################################################
# parsing parameters
while getopts "k:r:b:o:d:s:a:" options
do
  case $options in
      k) BZIMAGE=$OPTARG;;
      b) GRUBSRC=$OPTARG;;
      r) ROOTFSEXT4=$OPTARG;;
      o) DISKIMG=$OPTARG;;
      d) DEVDISK=$OPTARG;;
      s) DISKIMG_SIZE=$OPTARG;;
      a) APP_PKG=$OPTARG;;
      \?) echo "option: $options, only -k -b -r -o  -d -s -a are valid option" 1>&2
          exit 1;;
  esac
done

echo -e "\n\n BOOTLOADER is $GRUBSRC,  KERNEL is $BZIMAGE,  ROOT FS is $ROOTFSEXT4"
echo -e " APP_PKG is $APP_PKG,       Result file:$DISKIMG \n"

if [ ! -d ${GRUBSRC} ]
then
    echo "Grub directory doesn't exist !!! "
    exit 2
fi

if [ ! -f $BZIMAGE ]
then
    echo "kernel image doesn't exist !!! "
    exit 3
fi

f [ ! -f $ROOTFSEXT4 ]
then
    echo "ROOTFS EXT3 file doesn't exist !!! "
    exit 4
fi


# do some clean up
cleanmount

echo -e "\n Creating raw disk image file with size ${DISKIMG_SIZE} MB..."
dd if=/dev/zero of=$DISKIMG bs=1M count=${DISKIMG_SIZE} >& /dev/null

losetup /dev/loop1 $DISKIMG >& /dev/null || { echo "Failed to set up /dev/loop1 to empty disk image: $DISKIMG "; exit 5; }
CREATECMD="n\np\n1\n\n\na\n1\nw\n"
echo -e $CREATECMD | fdisk /dev/loop1  >& /dev/null
let offset=`fdisk -u -l /dev/loop1 | tail -1 | awk '{print $3}'`*512
losetup -o $offset /dev/loop2 /dev/loop1  >& /dev/null || { echo "Failed to set up /dev/loop2 ! "; cleanmount; exit 6; }
sync

mke2fs -j /dev/loop2 >& /dev/null
sync; sync;
sleep 2

[ -d loop2_mnt ] && { rm -rf loop2_mnt; }
mkdir loop2_mnt

mount /dev/loop2 loop2_mnt || { echo "Failed to mount /dev/loop2 loop2_mnt ! "; cleanmount; exit 7; }

[ -d ext4_mnt ] && { rm -rf ext4_mnt; }
mkdir ext4_mnt

mount $ROOTFSEXT4 ext4_mnt -o loop || { echo "Failed to mount $ROOTFSEXT4 loop2_mnt ! "; cleanmount; exit 8; }

echo -e "\n copy ROOTFS from ext4_mnt into loop2_mnt..."
cp -rf ext4_mnt/* loop2_mnt/

if [ "${APP_PKG}" != "" ]
then
    echo -e "\n adding application packages .."
    cd loop2_mnt
    tar xzvf ../${APP_PKG} --no-same-owner
    cd ..
fi

sync; sync

echo -e "\n Copy kernel image ..."
rm -f loop2_mnt/boot/bzImage
cp -f $BZIMAGE loop2_mnt/boot/bzImage
sync; sync

echo -e "\n copy grub binary from $GRUBSRC ..."
mkdir -p loop2_mnt/boot/grub
cp -rf $GRUBSRC/* loop2_mnt/boot/grub/
sync; sync; sync
sleep 2

echo -e "\n install grub in disk image..."
#/usr/sbin/grub --batch  --device-map=/dev/null <<EOF
./grub --batch  --device-map=/dev/null <<EOF
device (hd0)  ${DISKIMG}
root (hd0,0)
setup (hd0)
quit
EOF

echo -e "\n set up grub config ..."
rm -f loop2_mnt/boot/grub/device.map
echo "(hd0)     /dev/$DEVDISK" > loop2_mnt/boot/grub/device.map
echo -e "\n device.map file: "
cat loop2_mnt/boot/grub/device.map

rm -f loop2_mnt/boot/grub/grub.conf
touch loop2_mnt/boot/grub/grub.conf
echo "default=0" >> loop2_mnt/boot/grub/grub.conf
echo "timeout=5" >> loop2_mnt/boot/grub/grub.conf
echo "title Linux Guest OS (yocto)" >> loop2_mnt/boot/grub/grub.conf
echo -e "\troot (hd0,0)" >> loop2_mnt/boot/grub/grub.conf
echo -e "\tkernel /boot/bzImage root=/dev/${DEVDISK}1 console=tty0 console=ttyS0,115200 udev.children-max=2" >> loop2_mnt/boot/grub/grub.conf

echo -e "\n grub.conf file: "
cat loop2_mnt/boot/grub/grub.conf
cp -f loop2_mnt/boot/grub/grub.conf loop2_mnt/boot/grub/menu.lst
sync; sync

# clean up
cleanmount

Image convertion between different types

Converting images from one format to another is generally straightforward.

The qemu-img convert command can do conversion between multiple formats, including qcow2, qed, raw, vdi, vhd, and vmdk.

$ # convert a raw image file named image.img to a qcow2 image file.
$ qemu-img convert -f raw -O qcow2 image.img image.qcow2

$ # convert a vmdk image file to a raw image file.
$ qemu-img convert -f vmdk -O raw image.vmdk image.img

$ # convert a vmdk image file to a qcow2 image file.
$ qemu-img convert -f vmdk -O qcow2 image.vmdk image.qcow2