Moving from Virtualbox to Physical Disk

I decided to convert one of my Gentoo VirtualBox installs to physical disk (ie. convert Virtual -> Physical ). This sounded pretty straightforward, but after a bit of Googling, all I had found were a bunch of articlesi explaining how to take a physical partition and convert it to a .vdi. This was exactly the opposite of what I wanted to do.

Finally, I found this wiki article, and it all came together.

Preparing the disks

I had a spare SATA drive laying around, so I powered off the machine and popped it in. Using my trusty Gentoo install CD, I partitioned the Linux disk. My Virtualbox install had a disk layout like this:

/dev/sda1   /boot
/dev/sda2   <swap>
/dev/sda3   /
/dev/sda4   <Extended>
/dev/sda5   /usr
/dev/sda6   /var

I basically mirrored that on the Linux drive. It's not necessary to be exact, but make sure each partition has AT LEAST enough space to hold the data you'll be moving to it. I also created one extra partition to hold the .vdi images temporarily while I got the Linux data extracted. I would be sure and create this partition LAST, and at the end of the drive, because once you delete, renumbering Linux partitions is a pain in the ass.

Copying the .vdi files

Since my windows install is full of games, and reinstalling would be a bitch, I wanted to get Gentoo copied over and working with my 2 Windows drives detached from the system to prevent me from accidentally overwriting, say, the partition table on one of them.

What I did is use the IFS driver to mount the extra partition I created above inside Windows, and copied my 2 Virtualbox .vdi's to it (Gentoo.vdi and Gentoo2.vdi). At this point, I shut down the machine and disconnected the 2 SATA cables from my Windows drives. Better safe than sorry.

Copying the Gentoo system

Again, I booted the box with the Gentoo install CD in the drive. It recognized its HDD as sda (as expected). Now it was time to start copying files. To make things easier, inside the Gentoo LiveCD environment, I made two directories, /mnt/old and /mnt/new. The names should be pretty self explanatory. Based on the wiki article above, I wrote a little shell script to make mounting the .vdi partitions easier. I'll include the script at the end of the post. For instance, to get the /boot partition mounted from the .vdi image and copied to the new Linux drive:

saturn-gentoo ~ # ./vdi.sh -v /mnt/cdrom/Virtual\ Disks/Gentoo.vdi
PART          OFFSET ID
1              65536 83
2           34095616 82
3          571351552 83
Which partition to you want to mount? (Enter to skip)> 1
Offset: 65536
Enter a mountpoint > /mnt/old
Filesystem type (ex. ext3) > ext2
Mount command:
   mount "/mnt/cdrom/Virtual Disks/Gentoo.vdi" "/mnt/old" -t ext2 \
      -o loop,offset=65536
Execute now? (Yn) > Y
Mount successful
saturn-gentoo ~ # mount /dev/sda1 /mnt/new
saturn-gentoo ~ # cp -axr /mnt/old/* /mnt/new
saturn-gentoo ~ # umount /mnt/old
saturn-gentoo ~ # umount /mnt/new

Rinse and repeat for the remainder of the partitions.

System configuration

Since the drive identifier (sda) was the same between VirtualBox and the physical install, I didn't have to touch fstab or grub. I made sure all partitions were unmounted and rebooted. With the CD out of the drive, I was pleasantly surprised to see the Grub prompt, and even more surprised when everything just booted normally. I had to do some minimal tweaking to X to move away from the virtualbox video/input drivers, but other than that, things Just Worked ®.

VDI script

Here's the script I used to mount my .vdi partitions. If anyone finds it useful, please let me know.

Disclaimer: This script may wipe out the ozone layer and promote anarchy among the masses. Use at your own risk.

#!/bin/bash
#==============================================================================
# Copyright 2010 Josh Williams <theprime@codingprime.com>
#
# Quick and dirty bash script to handle mounting .vdi images from VirtualBox
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#===============================================================================

TEMP_FILENAME='/tmp/vdimage'
VDI_FILENAME=''
DUMP_ONLY=0

#
# Print the script usage information
#
function usage() {
  cat << EOF
USAGE: $0 options FILENAME

OPTIONS:
    -h           Show this message
    -p           Print the VDI partition table and exit.
    -t filename  Override the temporary file (default
                 is ./vdimage)
    -v filename  Path to the .vdi file
EOF
}

#
# Print the extracted partition table
#
function print_partitions() {
  echo "PART          OFFSET ID"
  for part in $@; do
    echo "${part}" | awk -F: '{printf "%-4s %15s %s\n", $1, $2, $3}'
  done
}

#
# Get the partition to be mounted
#
function select_partition() {
  while true; do
    print_partitions ${valid_partitions[@]}
    read -p "Which partition to you want to mount? (Enter to skip)> " part_num
    if [ -n "${part_num}" ]; then
      for (( i = 0 ; i < ${#valid_partitions[@]} ; i++ )); do
        part=${valid_partitions[i]}
        part_id=`echo "${part}" | awk -F: '{print $1;}'`
        if [ ${part_id} == $part_num ]; then
          num=`echo "${part}" | awk -F: '{print $1;}'`
          offset=`echo "${part}" | awk -F: '{print $2;}'`
          typ_id=`echo "${part}" | awk -F: '{print $3;}'`

          selected_partition=( $num $offset $typ_id )
          break
        fi
      done
    fi

    if [ -z "${selected_partition}" ]; then
      if [ -z "${part_num}" ]; then
        break
      else
        echo "Invalid partition number: ${part_num}"
        continue
      fi
    else
      break
    fi
  done
}

#
# Get the mountpoint
#
function select_mountpoint() {
  echo "Offset: ${selected_partition[1]}"
  while true; do
    read -p "Enter a mountpoint > " mountpoint
    if [ -n "${mountpoint}" ]; then
      if [ -d "${mountpoint}" ]; then
        break
      else
        echo "Not a valid directory: ${mountpoint}"
        continue
      fi
    else
      break
    fi
  done
}

#
# Select Filesystem type
#
function select_fs_type() {
  supported_filesystems=`cat /proc/filesystems | \
    sed 's/^nodev//' | awk '{print $1;}'`
  while true; do
    valid_fs_type=0
    read -p "Filesystem type (ex. ext3) > " fs_type
    if [ -n "$fs_type" ]; then
      for i in ${supported_filesystems}; do
        if [[ $i == ${fs_type} ]]; then
          valid_fs_type=1
          break
        fi
      done
      if (( $valid_fs_type )); then
        break
      else
        echo "Filesystem type not supported: ${fs_type}"
        echo ""
        fs_type=""
      fi
    else
      break
    fi
  done
}

#
# Handle the actual mounting
#
function handle_mount() {
  echo "Mount command:"
  cmd='mount "'${VDI_FILENAME}'" "'${mountpoint}'" -t '${fs_type}' \
    -o loop,offset='${selected_partition[1]}
  echo "   ${cmd}"
  while true; do
    read -p "Execute now? (Yn) > " answer
    if [ ! -n "${answer}" ]; then answer="Y"; fi
    case $answer in
    [yY])
      mount "${VDI_FILENAME}" "${mountpoint}" -t ${fs_type} -o \
        loop,offset=${selected_partition[1]}
      break
      ;;
    [nN])
      break
      ;;
    *)
      echo "Invalid response: ${answer}\n"
      ;;
    esac
  done
}

#
# Check the arguments
#
while getopts "hv:t:p" OPTION
do
  case $OPTION in
    h)
      usage
      exit 1
      ;;
    v)
      VDI_FILENAME=$OPTARG
      ;;
    t)
      TEMP_FILENAME=$OPTARG
      ;;
    p)
      DUMP_ONLY=1
      ;;
    ?)
      usage
      exit 1
      ;;
  esac
done

#
# Make sure the .vdi file actually exists
#
if [[ -z $VDI_FILENAME ]]; then usage; exit 1; fi

#
# Make sure we're dealing with a fixed size image
#
fixed=`od -j76 -N4 -td4 "${VDI_FILENAME}" | awk 'NR==1{print 2;}'`
if [ $fixed != "2" ]; then echo "Not a fixed disk."; exit; fi

#
# Find the initial offset
#
initial_offset=`od -j344 -N4 -td4 "${VDI_FILENAME}" | awk 'NR==1{print $2;}'`

#
# Extract the partition table
#
dd if="${VDI_FILENAME}" of=${TEMP_FILENAME} bs=1 skip=${initial_offset} \
  count=512 >> /dev/null 2>&1
parts=`sfdisk -luS ${TEMP_FILENAME} 2>&1 | grep -E "^ ?${TEMP_FILENAME}" | \
  sed -E "s|${TEMP_FILENAME}||" | sed 's/*//' | \
  awk '{gsub(/ */,"",$1); print $1 ":" $2 ":" $5;}'`

#
# Cleanup the Temp file
#
if [ -e ${TEMP_FILENAME} ]; then
  rm ${TEMP_FILENAME}
fi

#
# Gather the partitions that are valid for mounting
# This basically drops anything that is an Extended partition ( Type 5 )
#
valid_partitions=( )
for part in $parts; do
  typ_id=`echo $part | awk -F: '{print $3;}'`
  if [ "${typ_id}" != "5" ]; then
    num=`echo "${part}" | awk -F: '{print $1;}'`
    offset=`echo "${part}" | awk -F: '{print $2;}'`
    offset=`echo "${offset} * 512 + $initial_offset" | bc`
    valid_partitions=( "${valid_partitions[@]}" "${num}:${offset}:${typ_id}" )
  fi
done


if (( $DUMP_ONLY )); then
  print_partitions ${valid_partitions[@]}
else
  while true; do
    selected_partition=""
    mountpoint=""
    fs_type=""

    select_partition

    if [ -n "${selected_partition}" ]; then
      select_mountpoint
    fi

    if [ -n "${mountpoint}" ]; then
      select_fs_type
    fi

    if [ -n "${fs_type}" ]; then
      handle_mount
    fi

    if [ ! -n "$fs_type" ]; then break; fi
  done
fi

Is it overkill? Yes. But I did it anyway. Next time: getting Windows back into the mix.


1391 Words

2010-08-21T15:50:00