Support --vmfile and --vmsize command line options for deploying a virtual machine.
authorMichael Prokop <mika@grml.org>
Mon, 30 May 2011 16:55:06 +0000 (18:55 +0200)
committerMichael Prokop <mika@grml.org>
Mon, 30 May 2011 20:40:57 +0000 (22:40 +0200)
Using the --vmfile option sets up a virtual machine instead of
deploying Debian to a block device or directory.

The --vmsize option configures the size of the virtual disk file
that's being deployed.

Thanks to Thorsten Glaser for help with installing Grub accordingly.

TODO
bootgrub.mksh [new file with mode: 0644]
cmdlineopts.clp
debian/control
debian/rules
grml-debootstrap
grml-debootstrap.8.txt

diff --git a/TODO b/TODO
index 7af02ee..d28e753 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,6 +1,7 @@
 TODO list for grml-debootstrap
 ==============================
 
+* support Virtual Machine deployment in interactive mode(?)
 * support for LVM
 * support tarballs for customization/mirror source
   -> integrate/use 'fai dirinstall'?
diff --git a/bootgrub.mksh b/bootgrub.mksh
new file mode 100644 (file)
index 0000000..6abc912
--- /dev/null
@@ -0,0 +1,277 @@
+#!/usr/bin/env mksh
+# $Id$
+# $miros: src/sys/arch/i386/stand/bootxx/mkbxinst.sh,v 1.28 2010/11/12 21:20:35 tg Exp $
+# $miros: src/sys/arch/i386/stand/bootxx/bootxx.S,v 1.26 2010/12/01 19:56:58 tg Exp $ +t:GRUB +s:MBR
+#-
+# Copyright (c) 2007, 2008, 2009, 2010
+#      Thorsten Glaser <tg@mirbsd.org>
+#
+# Provided that these terms and disclaimer and all copyright notices
+# are retained or reproduced in an accompanying document, permission
+# is granted to deal in this work without restriction, including un‐
+# limited rights to use, publicly perform, distribute, sell, modify,
+# merge, give away, or sublicence.
+#
+# This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
+# the utmost extent permitted by applicable law, neither express nor
+# implied; without malicious intent or gross negligence. In no event
+# may a licensor, author or contributor be held liable for indirect,
+# direct, other damage, loss, or other issues arising in any way out
+# of dealing in the work, even if advised of the possibility of such
+# damage or existence of a defect, except proven that it results out
+# of said person’s immediate fault when using the work as intended.
+#-
+# Self-installing 32-bit x86 boot blocks for GNU GRUB2 on i386-pc
+# Reads a list of extents (firstblock lastblock) from standard input
+# and writes bootxx to standard output, which can subsequentially be
+# stored as partition boot record (or floppy boot sector) on disc.
+
+set -A thecode 0x66 0x31 0xC9 0x8E 0xD1 0xBC 0xFC 0x7B 0x66 0x51 0x66 0x9D 0x8E 0xC1 0xBE 0x00 0x7C 0x8E 0xD9 0xBB 0x00 0x08 0x53 0x53 0x8E 0xC3 0xBF 0x00 0xFE 0xB5 0x02 0xF3 0xA4 0x1F 0x68 0x6B 0xFE 0xCB 0x30 0x41 0x41 0x44 0x20 0x4C 0x6F 0x61 0x64 0x69 0x6E 0x67 0x20 0x00 0x20 0x65 0x72 0x72 0x6F 0x72 0x0D 0x0A 0x00 0x96 0x02 0x00 0x12 0x00 0x00 0x80 0x65 0xFF 0xB4 0x0E 0xBB 0x07 0x00 0xCD 0x10 0xAC 0x08 0xC0 0x75 0xF4 0xC3 0xE8 0xF7 0xFF 0xB8 0x41 0xFF 0x87 0x06 0x44 0xFE 0x3D 0x41 0xFF 0x75 0x19 0x31 0xC0 0xCD 0x16 0xEA 0xF0 0xFF 0x00 0xF0 0xFB 0x88 0x16 0x43 0xFE 0xBE 0x26 0xFE 0x80 0xFA 0x80 0x72 0xDB 0xE8 0xD2 0xFF 0x31 0xDB 0xBE 0x68 0xFF 0x0F 0xB6 0x2E 0x3D 0xFE 0xAC 0x0F 0xB6 0xC8 0xC0 0xE9 0x05 0x83 0xE0 0x1F 0x40 0x41 0x50 0xBF 0x10 0xFE 0xB8 0x10 0x00 0xAB 0xB0 0x01 0xAB 0x89 0xD8 0xAB 0x8C 0xC8 0xAB 0xF3 0xA4 0x31 0xC0 0xAB 0xAB 0xAB 0xAB 0x5F 0x55 0x56 0xBE 0x10 0xFE 0xFF 0x16 0x44 0xFE 0xBD 0x04 0x00 0x8A 0x16 0x43 0xFE 0x60 0xF9 0xCD 0x13 0x9C 0xB8 0x2E 0x0E 0xBB 0x07 0x00 0xCD 0x10 0x9D 0xFB 0x61 0x73 0x1C 0x4D 0x60 0x9C 0x31 0xC0 0xCD 0x13 0x9D 0xBE 0x34 0xFE 0x0F 0x84 0x77 0xFF 0xB8 0x30 0x0E 0x01 0xE8 0xBB 0x07 0x00 0xCD 0x10 0x61 0xEB 0xD2 0xB4 0x02 0x00 0xE7 0x89 0x5C 0x04 0x00 0xFC 0x72 0x14 0x66 0x83 0x44 0x08 0x01 0x66 0x83 0x54 0x0C 0x00 0x4F 0x75 0xAC 0x5E 0x5D 0x4D 0x0F 0x85 0x7D 0xFF 0xBE 0x3A 0xFE 0xE8 0x3F 0xFF 0x66 0x31 0xD2 0x52 0x66 0x4A 0x8A 0x16 0x43 0xFE 0xB8 0x00 0x82 0x50 0xFA 0xCB 0x60 0x06 0xB4 0x08 0x8A 0x16 0x43 0xFE 0xF9 0xCD 0x13 0xFB 0x07 0xBE 0x34 0xFE 0x0F 0x82 0x21 0xFF 0x80 0xE1 0x3F 0x88 0x0E 0x40 0xFE 0x0F 0xB6 0xC6 0x40 0xA3 0x3E 0xFE 0x61 0x8B 0x0E 0x40 0xFE 0xE3 0xD7 0x8B 0x44 0x08 0x8B 0x54 0x0A 0xF7 0xF1 0x42 0x31 0xC9 0x87 0xD1 0xF7 0x36 0x3E 0xFE 0xC0 0xE4 0x06 0x86 0xC4 0x09 0xC1 0x88 0xD6 0xB8 0x01 0x02 0xC3 0xB4 0x42 0xC3
+typeset -i ofs_bkcnt=61
+typeset -i ofs_geomh=62
+typeset -i ofs_geoms=64
+typeset -i ofs_partp=66
+typeset -i ofs_secsz=234
+typeset -i begptr=360
+typeset -Uui8 thecode
+
+typeset -Uui16 curptr=begptr
+typeset -i wnum=0 wofs=0 wrec=0 bkend=0x1FE
+
+function do_record {
+       typeset -Ui blk=$1 cnt=$2 n
+       typeset -Uui16 x=blk y
+
+       (( blk && cnt )) || return
+
+       print -u2 "$wrec @0x${curptr#16#}: $cnt @$blk (0x${x#16#})"
+
+       while (( cnt )); do
+               let wrec++
+               (( n = blk < 0x00000100 ? 0 :
+                   blk < 0x00010000 ? 1 :
+                   blk < 0x01000000 ? 2 : 3 ))
+               (( x = cnt < 33 ? cnt : 32 ))
+               (( y = blk ))
+               print -u2 " - 0x${curptr#16#}: $((x)) (0x${x#16#}) @ $blk" \
+                   "(0x${y#16#})"
+               (( thecode[curptr++] = (n++ << 5) | (x - 1) ))
+               (( blk += x ))
+               (( cnt -= x ))
+               while (( n-- )); do
+                       (( thecode[curptr++] = y & 0xFF ))
+                       (( y >>= 8 ))
+               done
+       done
+}
+
+function record_block {
+       typeset -Ui blk=$1
+
+       if (( !blk || (wofs && blk != (wofs + wnum)) )); then
+               # flush the blocks from the cache
+               (( wnum )) && do_record $wofs $wnum
+               wofs=0
+               wnum=0
+       fi
+       if (( blk )); then
+               # record some new block into the cache
+               (( wofs )) || let wofs=blk
+               (( wnum += 1 << sscale ))
+       fi
+}
+
+typeset -i partp=0 numheads=0 numsecs=0 sscale=0 bsh=9 mbrpno=0 mbrptp=0 pofs=0
+set -A g_code 0 0 0
+
+while getopts ":0:1AB:g:h:M:O:p:S:s:" ch; do
+       case $ch {
+       (0)     ;;
+       (1)     ;;
+       (A)     numheads=0
+               numsecs=99
+               ;;
+       (B)     if (( (bsh = OPTARG) < 9 || OPTARG > 15 )); then
+                       print -u2 Error: invalid block size "2^'$OPTARG'"
+                       exit 1
+               fi
+               ;;
+       (g)     if [[ $OPTARG != +([0-9]):+([0-9]):+([0-9]) ]]; then
+                       print -u2 Error: invalid geometry code "'$OPTARG'"
+                       exit 1
+               fi
+               saveIFS=$IFS
+               IFS=:
+               set -A g_code -- $OPTARG
+               IFS=$saveIFS ;;
+       (h)     if (( (numheads = OPTARG) < 1 || OPTARG > 256 )); then
+                       print -u2 Warning: invalid head count "'$OPTARG'"
+                       numheads=0
+               fi ;;
+       (M)     if [[ $OPTARG != +([0-9])?(:?(0[Xx])+([0-9])) ]]; then
+                       print -u2 Warning: invalid partition info "'$OPTARG'"
+                       mbrpno=0
+                       mbrptp=0
+               else
+                       saveIFS=$IFS
+                       IFS=:
+                       set -A mbr_code -- $OPTARG
+                       IFS=$saveIFS
+                       (( mbrpno = mbr_code[0] ))
+                       (( mbrptp = mbr_code[1] ))
+                       if (( mbrpno < 1 || mbrpno > 4 )); then
+                               print -u2 Warning: invalid partition \
+                                   number "'$OPTARG'"
+                               mbrpno=0
+                       fi
+                       if (( mbrptp < 1 || mbrptp > 255 )); then
+                               print -u2 Warning: invalid partition \
+                                   type "'$OPTARG'"
+                               mbrptp=0
+                       fi
+               fi ;;
+       (O)     if [[ $OPTARG != +([0-9]) ]]; then
+                       print -u2 Warning: invalid partition offset "'$OPTARG'"
+               else
+                       pofs=$OPTARG
+               fi ;;
+       (p)     if (( (partp = OPTARG) < 1 || OPTARG > 255 )); then
+                       print -u2 Warning: invalid partition type "'$OPTARG'"
+                       partp=0
+               fi ;;
+       (S)     if (( (sscale = OPTARG) < 0 || OPTARG > 24 )); then
+                       print -u2 Error: invalid input scale "'$OPTARG'"
+                       exit 1
+               fi ;;
+       (s)     if (( (numsecs = OPTARG) < 1 || OPTARG > 63 )); then
+                       print -u2 Warning: invalid sector count "'$OPTARG'"
+                       numsecs=0
+               fi ;;
+       (*)     print -u2 'Syntax:
+       bxinst [-1A] [-B blocksize] [-g C:H:S] [-h heads] [-M pno(1..4)[:typ]]
+           [-O partitionofs] [-p type] [-S scale] [-s secs] <sectorlist | \\
+           dd of=image conv=notrunc
+Default values: blocksize=9 heads=16 sectors=63 part.ofs=0 type=0x27 scale=0
+    partno=4 if -g (create MBR partition) is given; -A = auto boot geometry'
+               exit 1 ;;
+       }
+done
+shift $((OPTIND - 1))
+
+typeset -Ui psz=0      # must be unsigned
+if (( g_code[0] )); then
+       # bounds check partition table values, calculate total sectors
+       if (( g_code[0] < 1 || g_code[1] < 1 || g_code[1] > 256 ||
+           g_code[2] < 1 || g_code[2] > 63 )); then
+               print -u2 Invalid geometry, values out of bounds.
+       elif [[ $(print "(${g_code[0]} * ${g_code[1]} * ${g_code[2]})" \
+           "> 4294967295" | bc) = 1 ]]; then
+               print -u2 Invalid geometry, more than 2 TiB of data.
+       else
+               # we know it's <= 2^32-1
+               (( psz = g_code[0] * g_code[1] * g_code[2] ))
+       fi
+fi
+if (( psz )); then
+       print -u2 geometry is $psz sectors \($(print \
+           "$psz * $((1 << bsh))" | bc) bytes\) in ${g_code[0]} cylinders, \
+           ${g_code[1]} heads, ${g_code[2]} sectors per track
+       if (( numsecs == 0 || (numsecs != 99 && numheads == 0) )); then
+               print -u2 Warning: using these values for C/H/S boot
+               numheads=${g_code[1]}
+               numsecs=${g_code[2]}
+       fi
+       (( mbrpno )) || mbrpno=4        # default partition number
+fi
+if (( mbrpno )); then
+       bkend=0x1BE
+       (( psz )) || print -u2 Warning: no geometry given, will not \
+           create an MBR partition table entry
+fi
+
+if (( numsecs == 99 )); then
+       numheads=0
+       numsecs=0
+else
+       if (( !numheads )); then
+               print -u2 Warning: using default value of 16 heads
+               numheads=16
+       fi
+
+       if (( !numsecs )); then
+               print -u2 Warning: using default value of 63 sectors
+               numsecs=63
+       fi
+fi
+
+# read in the extents
+while read firstblock lastblock junk; do
+       while (( firstblock <= lastblock )); do
+               record_block $((firstblock++ << sscale))
+       done
+done
+record_block 0 # just flush
+print -u2 "using $wrec blocks, $((curptr-begptr)) bytes ($((bkend-curptr)) free)"
+
+# fill the block table
+if (( curptr-- > bkend )); then
+       print -u2 Error: too many blocks
+       exit 1
+fi
+while (( ++curptr < bkend )); do
+       thecode[curptr]=0
+done
+thecode[510]=0x55
+thecode[511]=0xAA
+
+# fill in other data
+(( thecode[ofs_bkcnt] = wrec ))
+(( thecode[ofs_geomh] = numheads & 0xFF ))
+(( thecode[ofs_geomh + 1] = numheads >> 8 ))
+(( thecode[ofs_geoms] = numsecs ))
+(( thecode[ofs_partp] = partp ))
+print -u2 "using sectors of 2^$bsh = $((1 << bsh)) bytes"
+(( thecode[ofs_secsz] = (1 << (bsh - 8)) ))
+
+# create an MBR partition if desired
+if (( psz )); then
+       (( mbrpno = 0x1BE + ((mbrpno - 1) * 16) ))
+       set -A o_code   # g_code equivalent for partition offset
+       (( o_code[2] = pofs % g_code[2] + 1 ))
+       (( o_code[1] = pofs / g_code[2] ))
+       (( o_code[0] = o_code[1] / g_code[1] + 1 ))
+       (( o_code[1] = o_code[1] % g_code[1] + 1 ))
+       # boot flag; C/H/S offset
+       thecode[mbrpno++]=0x80
+       (( thecode[mbrpno++] = o_code[1] - 1 ))
+       (( cylno = o_code[0] > 1024 ? 1023 : o_code[0] - 1 ))
+       (( thecode[mbrpno++] = o_code[2] | ((cylno & 0x0300) >> 2) ))
+       (( thecode[mbrpno++] = cylno & 0x00FF ))
+       # partition type; C/H/S end
+       (( thecode[mbrpno++] = (mbrptp ? mbrptp : partp ? partp : 0x27) ))
+       (( thecode[mbrpno++] = g_code[1] - 1 ))
+       (( cylno = g_code[0] > 1024 ? 1023 : g_code[0] - 1 ))
+       (( thecode[mbrpno++] = g_code[2] | ((cylno & 0x0300) >> 2) ))
+       (( thecode[mbrpno++] = cylno & 0x00FF ))
+       # partition offset, size (LBA)
+       (( thecode[mbrpno++] = pofs & 0xFF ))
+       (( thecode[mbrpno++] = (pofs >> 8) & 0xFF ))
+       (( thecode[mbrpno++] = (pofs >> 16) & 0xFF ))
+       (( thecode[mbrpno++] = (pofs >> 24) & 0xFF ))
+       (( pssz = psz - pofs ))
+       (( thecode[mbrpno++] = pssz & 0xFF ))
+       (( thecode[mbrpno++] = (pssz >> 8) & 0xFF ))
+       (( thecode[mbrpno++] = (pssz >> 16) & 0xFF ))
+       (( thecode[mbrpno++] = (pssz >> 24) & 0xFF ))
+fi
+
+# create the output string
+ostr=
+curptr=0
+while (( curptr < 512 )); do
+       ostr=$ostr\\0${thecode[curptr++]#8#}
+done
+
+# over and out
+print -n "$ostr"
+exit 0
index 0681c6b..6479dbc 100644 (file)
@@ -12,7 +12,7 @@
 # should be handled in the main script, where it belongs.
 ################################################################################
 
-CMDLINE_OPTS=mirror:,iso:,release:,target:,mntpoint:,debopt:,interactive,nodebootstrap,nopackages,filesystem:,config:,confdir:,packages:,chroot-scripts:,scripts:,pre-scripts:,debconf:,keep_src_list,hostname:,password:,bootappend:,grub:,arch:,insecure,verbose,help,version
+CMDLINE_OPTS=mirror:,iso:,release:,target:,mntpoint:,debopt:,interactive,nodebootstrap,nopackages,filesystem:,config:,confdir:,packages:,chroot-scripts:,scripts:,pre-scripts:,debconf:,vmfile,vmsize:,keep_src_list,hostname:,password:,bootappend:,grub:,arch:,insecure,verbose,help,version
 
 _opt_temp=`getopt --name grml-debootstrap -o +m:i:r:t:p:c:d:vhV --long \
     $CMDLINE_OPTS -- "$@"`
@@ -38,6 +38,12 @@ while :; do
   --target|-t)         # Target partition (/dev/...) or directory
     shift; _opt_target="$1"
     ;;
+  --vmfile)           # Virtual machine file
+    _opt_vmfile="T"
+    ;;
+  --vmsize)           # size of Virtual machine file
+    shift; _opt_vmsize="$1"
+    ;;
   --mntpoint|-p)       # Mountpoint used for mounting the target system
     shift; _opt_mntpoint="$1"
     ;;
index afb7ea0..05e27a1 100644 (file)
@@ -13,6 +13,7 @@ Vcs-Browser: http://git.grml.org/?p=grml-debootstrap.git
 Package: grml-debootstrap
 Architecture: all
 Depends: ${shlibs:Depends}, ${misc:Depends}, debootstrap (>= 0.3.3.3) | cdebootstrap (>= 0.3.16), grml-etc-core
+Recommends: kpartx, mksh, parted, qemu-utils
 Description: wrapper around debootstrap for installing plain Debian via Grml
  This package provides a wrapper suite around deboostrap and
  cdebootstrap for installing a plain Debian system via Grml.
index e9aada2..ef04941 100755 (executable)
@@ -40,6 +40,7 @@ install: build
        install -m 755 grml-debootstrap debian/grml-debootstrap/usr/sbin/
        install -m 644 zsh-completion   debian/grml-debootstrap/etc/zsh/completion.d/_grml-debootstrap
        install -m 644 cmdlineopts.clp  debian/grml-debootstrap/usr/share/grml-debootstrap/functions/cmdlineopts.clp
+       install -m 644 bootgrub.mksh    debian/grml-debootstrap/usr/share/grml-debootstrap/bootgrub.mksh
 
 # Build architecture-dependent files here.
 binary-arch: build install
index 5b426dc..111e8e8 100755 (executable)
@@ -15,6 +15,9 @@ VERSION="$(dpkg --list $PN 2>/dev/null| awk '/^i/ {print $3}')"
 VERSION="${VERSION:-unknown}"
 MNTPOINT="/mnt/debootstrap.$$"
 
+# defaults
+VMSIZE="2G"
+
 # inside the chroot system locales might not be available, so use minimum:
 export LANG=C
 export LC_ALL=C
@@ -39,7 +42,7 @@ Bootstrap options:
 
   -m, --mirror <URL>     Mirror which should be used for apt-get/aptitude.
   -i, --iso <mnt>        Mountpoint where a Debian ISO is mounted to, for use
-                           instead of fetching packages from a mirror.
+                         instead of fetching packages from a mirror.
   -r, --release <name>   Release of new Debian system (default: squeeze).
   -t, --target <target>  Target partition (/dev/...) or directory where the
                          system should be installed to.
@@ -50,9 +53,18 @@ Bootstrap options:
       --nodebootstrap    Skip debootstrap, only do configuration to the target.
       --grub <device>    Target for grub installation. Usage example: /dev/sda
       --arch <arch>      Architecture to use. Currently only i386 is supported.
-      --filesystem <fs>  Filesystem that should be used when target is a partition.
+      --filesystem <fs>  Filesystem that should be used when target is a partition
+                         or Virtual Machine (see --vmfile).
       --insecure         Do not download and check Release file signatures.
 
+Options for Virtual Machine deployment:
+
+      --vmfile           Set up a Virtual Machine instead of installing to
+                         a partition or directory, to be combined with --target,
+                         like: --vmfile --target /mnt/sda1/qemu.img
+      --vmsize <size>    Use specified size for size of VM file (default: 2G).
+                         Syntax as supported by qemu-img, like: --vmsize 3G
+
 Configuration options:
 
   -c, --config <file>      Use specified configuration file, defaults to
@@ -131,6 +143,8 @@ fi
 [ "$_opt_iso" ]                 && ISO=$_opt_iso
 [ "$_opt_release" ]             && RELEASE=$_opt_release
 [ "$_opt_target" ]              && TARGET=$_opt_target
+[ "$_opt_vmfile" ]              && VIRTUAL=1
+[ "$_opt_vmsize" ]              && VMSIZE=$_opt_vmsize
 [ "$_opt_mntpoint" ]            && MNTPOINT=$_opt_mntpoint
 [ "$_opt_debopt" ]              && DEBOOTSTRAP_OPT=$_opt_debopt
 [ "$_opt_interactive" ]         && INTERACTIVE=1
@@ -172,6 +186,12 @@ if ! check4root ; then
 fi
 # }}}
 
+# make sure we have what we need {{{
+if [ -n "$VIRTUAL" ] ; then
+  check4progs kpartx mksh qemu-img || exit 1
+fi
+# }}}
+
 # source specified configuration file {{{
 if [ -n "$CONFIGFILE" ] ; then
    einfo "Reading specified config file $CONFIGFILE."
@@ -512,12 +532,22 @@ else # if not running automatic installation display configuration and prompt fo
    # do not display if MNTPOINT is the default one
    case "$MNTPOINT" in /mnt/debootstrap*) ;; *) echo "   Mount point:     $MNTPOINT" ;; esac
 
-   [ -n "$GRUB" ]     && echo "   Install grub:    $GRUB" || echo "   Install grub:    no"
+   if [ -n "$VIRTUAL" ] ; then
+      echo "   Install grub:    yes"
+   else
+     [ -n "$GRUB" ]     && echo "   Install grub:    $GRUB" || echo "   Install grub:    no"
+   fi
+
    [ -n "$RELEASE" ]  && echo "   Using release:   $RELEASE"
    [ -n "$MIRROR" ]   && echo "   Using mirror:    $MIRROR"
    [ -n "$HOSTNAME" ] && echo "   Using hostname:  $HOSTNAME"
    [ -n "$ISO" ]      && echo "   Using ISO:       $ISO"
+   if [ -n "$VIRTUAL" ] ; then
+      echo "   Deploying as Virtual Machine."
+      [ -n "$VMSIZE" ] && echo "   Using Virtual Disk file with size of ${VMSIZE}."
+   fi
 
+   echo
    echo "   Important! Continuing will delete all data from ${TARGET}!"
 
    echo
@@ -603,7 +633,7 @@ set_target_directory(){
     TARGET="$(readlink -f $TARGET)"
 }
 
-if [ -b "$TARGET" ] ; then
+if [ -b "$TARGET" ] || [ -n "$VIRTUAL" ] ; then
     PARTITION=1
 else
     set_target_directory
@@ -683,6 +713,11 @@ bailout(){
      fi
   fi
 
+  if [ -n "${ORIG_TARGET}" ] ; then
+    einfo "Removing loopback mount of file ${ORIG_TARGET}."
+    kpartx -d "${ORIG_TARGET}" ; eend $?
+  fi
+
   [ -n "$1" ] && EXIT="$1" || EXIT="1"
   [ -n "$3" ] && einfo "Notice: just remove $STAGES/$3 to reexecute the stage"
 
@@ -723,7 +758,7 @@ mkfs() {
        # will fail to detect the uuid in the chroot
        if echo "$TARGET" | grep -q "/dev/md" ; then
          blockdev --rereadpt "${TARGET}"
-       else
+       elif ! [ -n "$VIRTUAL" ] ; then
          blockdev --rereadpt "${TARGET%%[0-9]*}"
        fi
        # give the system 2 seconds, otherwise we might run into
@@ -758,8 +793,11 @@ mount_target() {
      if grep -q $TARGET /proc/mounts ; then
         ewarn "$TARGET already mounted, continuing anyway." ; eend 0
      else
-       [ -d "$MNTPOINT" ] || mkdir -p "$MNTPOINT"
+       if ! [ -d "${MNTPOINT}" ] ; then
+          [ -n "$VIRTUAL" ] || mkdir -p "${MNTPOINT}"
+       fi
        einfo "Mounting $TARGET to $MNTPOINT"
+       mkdir -p "$MNTPOINT"
        mount -o rw,suid,dev $TARGET $MNTPOINT
        eend $?
      fi
@@ -773,6 +811,90 @@ mount_target() {
 }
 # }}}
 
+# prepare VM image for usage with debootstrap {{{
+prepare_vm() {
+  if [ -z "$VIRTUAL" ] ; then
+     return 0 # be quite by intention
+  fi
+
+  if [ -b "$TARGET" ] ; then
+     eerror "Error: specified virtual disk target ($TARGET) is an existing block device."
+     eend 1
+     exit 1
+  fi
+
+  ORIG_TARGET="$TARGET" # store for later reuse
+
+  qemu-img create -f raw "${TARGET}" "${VMSIZE}"
+  echo 4 66 | mksh /usr/share/grml-debootstrap/bootgrub.mksh -A | dd of="$TARGET" conv=notrunc
+  dd if=/dev/zero bs=1 conv=notrunc count=64 seek=446 of="$TARGET"
+  parted -s "${TARGET}" 'mkpart primary ext3 2M -1'
+
+  DEVINFO=$(kpartx -av $TARGET) # 'add map loop1p1 (253:0): 0 6289408 linear /dev/loop1 2048'
+  if [ -z "${DEVINFO}" ] ; then
+    echo  Error setting up loopback device >&2
+    exit 1
+  fi
+
+  # hopefully this always works as expected
+  LOOP=$(echo ${DEVINFO} | sed 's/.* linear //; s/ [[:digit:]]*//') # '/dev/loop1'
+  BLOCKDEV=$(echo "${DEVINFO}" | sed -e 's/.* (\(.*:.*\)).*/\1/')   # '253:0'
+  LOOP_PART="$(echo ${DEVINFO##add map } | sed 's/ .*//')" # '/dev/loop1p1'
+  export TARGET="/dev/mapper/$LOOP_PART" # '/dev/mapper/loop1p1'
+
+  blockdev --rereadpt "${LOOP}"
+
+  if [ -z "$TARGET" ] ; then
+     echo "Error: target could not be set to according /dev/mapper/* device." >&2
+     exit 1
+  fi
+}
+# }}}
+
+# make VM image bootable and unmount it {{{
+finalize_vm() {
+  if [ -z "${VIRTUAL}" ] ; then
+     return 0
+  fi
+
+  if ! mount "${TARGET}" "${MNTPOINT}" ; then
+    eerror "Error: Mounting ${TARGET} failed, can not continue." ; eend 1
+    exit 1
+  fi
+
+  einfo "Installing Grub as bootloader."
+  mount -t proc none "${MNTPOINT}"/proc
+  mount -t sysfs none "${MNTPOINT}"/sys
+  mount --bind /dev "${MNTPOINT}"/dev
+
+  mkdir -p "${MNTPOINT}/boot/grub"
+  if ! [ -d "${MNTPOINT}"/usr/lib/grub/i386-pc/ ] ; then
+     eerror "Error: grub not installed inside Virtual Machine. Can not install bootloader." ; eend 1
+     exit 1
+  fi
+
+  cp "${MNTPOINT}"/usr/lib/grub/i386-pc/* "${MNTPOINT}/boot/grub/"
+  chroot "${MNTPOINT}" grub-mkimage -O i386-pc -p "(hd0,msdos1)/boot/grub" -o /tmp/core.img biosdisk part_msdos ext2
+  dd if="${MNTPOINT}/tmp/core.img" of="${ORIG_TARGET}" conv=notrunc seek=4
+  rm -f "${MNTPOINT}/tmp/core.img"
+
+  einfo "Updating grub configuration file."
+  chroot "${MNTPOINT}" update-grub
+
+  umount "${MNTPOINT}"/proc
+  umount "${MNTPOINT}"/sys
+  umount "${MNTPOINT}"/dev
+
+  einfo "Adjusting grub.cfg for successful boot sequence."
+  # ugly but needed to boot grub acordingly
+  sed -i "s;set root=.*;set root='(hd0,msdos1)';" "${MNTPOINT}"/boot/grub/grub.cfg
+  sed -i "s; root=/dev/.*; root=/dev/sda1;" "${MNTPOINT}"/boot/grub/grub.cfg
+
+  umount "${MNTPOINT}"
+  kpartx -d "${ORIG_TARGET}" >/dev/null
+}
+# }}}
+
 # install main chroot {{{
 debootstrap_system() {
   if [ "$_opt_nodebootstrap" ]; then
@@ -985,9 +1107,9 @@ fscktool() {
 # }}}
 
 # now execute all the functions {{{
-for i in mkfs tunefs mount_target debootstrap_system preparechroot \
-         execute_pre_scripts chrootscript execute_scripts umount_chroot   \
-         fscktool ; do
+for i in prepare_vm mkfs tunefs mount_target debootstrap_system \
+         preparechroot execute_pre_scripts chrootscript execute_scripts \
+         umount_chroot finalize_vm fscktool ; do
     if stage "${i}" ; then
        $i && ( stage "${i}" done && rm -f "${STAGES}/${i}" ) || bailout 2 "i"
     fi
index 4ae89b2..3144ebe 100644 (file)
@@ -165,6 +165,18 @@ Options and environment variables
 *-v*, *--verbose*::
     Increase verbosity.
 
+*--vmfile*::
+    Set up a Virtual Machine instead of installing to a partition or directory.
+    This allows deployment of a Virtual Machine. The options needs to be
+    combined with the --target option.
+    Usage example: --vmfile --target /mnt/sda1/qemu.img
+
+*--vmsize* <_size_>::
+   Use specified size for size of Virtual Machine disk file. If not specified it
+   defaults to 2G (being 2GB). Syntax as supported by qemu-img (see manpage
+   qemu-img(1) for details.
+   Usage example: --vmsize 3G
+
 *-V*, *--version*::
     Show version of program and exit.
 
@@ -193,6 +205,12 @@ any bootloader).
 
   grml-debootstrap --target /dev/sda3 --grub /dev/sda  --mirror ftp://ftp.tugraz.at/mirror/debian
 
+Install default debian release (stable/squeeze) in a Virtual Machine file with
+3GB disk size (including Grub as bootmanager in MBR of the virtual disk file):
+
+  mount /dev/sda1 /mnt/sda1
+  grml-debootstrap --vmfile --vmsize 3G --target /mnt/sda1/qemu.img
+
 Install default Debian release (stable/squeeze) on /dev/sda3 and install bootmanager
 Grub in MBR (master boot record) of /dev/sda and use /dev/sda3 as system partition.
 Use specified mirror instead of the default (ftp://ftp.debian.de/debian) one.