#!/bin/bash
#
# ovirt-early Start early ovirt services
#
# chkconfig: 3 01 99
# description: ovirt-early services
#

# Source functions library
. /etc/init.d/functions
. /etc/init.d/ovirt-functions

# size of the oVirt partition in megabytes
OVIRT_SIZE=64

configure_from_network() {
    DEVICE=$1
    if [ -n "$DEVICE" ]; then
        printf .
        # setup temporary interface to retrieve configuration
        echo "network --device $DEVICE --bootproto dhcp" | nash
        if [ $? -eq 0 ]; then
            printf .
            # from network-scripts/ifup-post
            IPADDR=$(LC_ALL=C ip -o -4 addr ls dev ${DEVICE} | awk '{ print $4 ; exit }')
            eval $(ipcalc --silent --hostname ${IPADDR} ; echo "status=$?")
            if [ "$status" = "0" ]; then
                hostname $HOSTNAME
                # retrieve remote config
                find_srv ovirt tcp
                if [ -n "$SRV_HOST" -a -n "$SRV_PORT" ]; then
                    printf .
                    cfgdb=$(mktemp)
                    wget -q -O $cfgdb \
                      "http://$SRV_HOST:$SRV_PORT/ovirt/cfgdb/$(hostname)"
                    if [ $? -eq 0 ]; then
                        printf .
                        augtool < $cfgdb > /dev/null 2>&1
                        if [ $? -eq 0 ]; then
                            printf "remote config applied."
                            return
                        fi
                    fi
                fi
            fi
        fi
    fi
    # default oVirt network configuration:
    # bridge each ethernet device in the system
    ETHDEVS=$(cd /sys/class/net && ls -d eth*)
    for eth in $ETHDEVS; do
        BRIDGE=ovirtbr`echo $eth | cut -b4-`
        printf '%s\n' "DEVICE=$eth" ONBOOT=yes "BRIDGE=$BRIDGE" \
            > /etc/sysconfig/network-scripts/ifcfg-$eth
        printf '%s\n' "DEVICE=$BRIDGE" BOOTPROTO=dhcp \
            ONBOOT=yes TYPE=Bridge PEERNTP=yes \
            > /etc/sysconfig/network-scripts/ifcfg-$BRIDGE
    done
    printf "default config applied."
}

# find_disk $bus $serial $live_disk
find_disk() {
    local bus=$1
    local serial=$2
    local live=$3
    for d in /dev/sd?; do
      eval $(udevinfo --query env --name $d)
      if [ "$ID_BUS" = "$bus" ]; then
        if [ -z "$serial" -o "$ID_SERIAL" = "$serial" ]; then
          if [ -n "$live" -a "$d" = "$live" ]; then
            # cannot install LiveOS over itself
            continue
          else
            echo $d
            return 0
          fi
        fi
      fi
    done
    return 1
}

# local_install $local_os $target
#  local_os - 1=install LiveOS and boot loader
#             0=initialize oVirt partition only
#  target - target disk to hold the oVirt partition
#           =usb|scsi[:serial#]
#  bootparams - extra boot parameters like console=
#
# oVirt partition is a local primary ext2 partition, labeled OVIRT
# content:
#  /config - local oVirt configuration (ktab, local admin password)
#  /boot - boot loader, kernel and initramfs
#  /LiveOS - oVirt Node compressed livecd image

local_install() {
    local local_os=$1
    local target=$2
    local bootparams=$3
    local disk
    local part
    local live_part
    local live_disk
    local live_dev=/dev/live
    if [ ! -e $live_dev ]; then
      # PXE boot
      live_dev=/dev/loop0
      live_part=$live_dev
      live_disk=
    else
      live_part=$(readlink -e $live_dev)
      live_disk=${live_disk%[1-9]}
    fi
    local ovirt_part=$(readlink -e /dev/disk/by-label/$OVIRT_LABEL)
    local ovirt_disk=${ovirt_part%[1-9]}
    if [ "$ovirt_disk" = "$ovirt_part" ]; then
      ovirt_disk=
    fi
    if [ -z "$target" ]; then
        if [ -z "$ovirt_disk" ]; then
          return 1
        fi
        # ovirt_init not specified, use pre-labeled oVirt partition
        mode=update
        disk=$ovirt_disk
        part=$ovirt_part
    else
      case "$target" in
          =)
          # empty ovirt_init, use current live image device
          mode=update
          disk=$live_disk
          part=$live_part
          ;;
          =scsi*)
          bus=scsi
          serial=${target#=scsi:}
          mode=install
          ;;
          =usb*)
          bus=usb
          serial=${target#=usb:}
          mode=install
          ;;
          *)
          return 1
          ;;
      esac
      if [ $mode = "install" ]; then
        if [ "$serial" = "=$bus" ]; then
          serial=
        fi
        disk=$(find_disk $bus $serial $live_disk)
        rc=$?
        if [ $rc -ne 0 ]; then
          echo "target disk '$target' not available"
          return 1
        fi
        if [ -n "$ovirt_disk" ]; then
          if [ "$disk" = "$ovirt_disk" ]; then
            # target disk contains oVirt partition, select it for update
            # TODO force reinstall option
            mode=update
            part=$ovirt_part
          else
            # remove label from oVirt partition, there can be only one
            e2label $ovirt_part ""
          fi
        fi
      fi
      if [ "$mode" = "install" ]; then
        printf "installing $disk..." | tee /dev/console
        dd if=/dev/zero bs=4096 count=1 of=$disk \
        && parted -s $disk mklabel msdos \
        && parted -s $disk mkpart primary ext2 0.0 $OVIRT_SIZE \
        && partprobe -s $disk
        if [ $? -ne 0 ]; then
          echo "$disk partition creation failed"; return 1
        fi
        part=${disk}1
        udevsettle
        mkfs.ext2 -L $OVIRT_LABEL $part \
        && tune2fs -c0 -i0 $part
        if [ $? -ne 0 ]; then
          echo "$disk ext2 creation failed"; return 1
        fi
        # update by-label link manually, mkfs won't trigger udev
        mkdir -p /dev/disk/by-label
        ln -sf $part /dev/disk/by-label/$OVIRT_LABEL
      fi
    fi

    if [ "$mode" = "update" ]; then
      printf "updating $disk..." | tee /dev/console
    fi
    ovirt=$(mktemp -d)
    if [ "$part" = "$live_part" ]; then
      # ovirt_init w/o specified target
      # setup /config on live disk, if writeable
      # TODO mlabel/e2label (check fs2 type or just blindly try?)
      mount -r $part $ovirt && mount -o remount,rw $ovirt \
      && mkdir -p $ovirt/config
      umount $ovirt && rmdir $ovirt
      return 0
    fi
    live=$(mktemp -d)
    mount -r $live_dev $live
    if [ $? -ne 0 ]; then
      echo "source image mount failed"; return 1
    fi
    mount $part $ovirt
    if [ $? -ne 0 ]; then
      echo "target mount failed"; return 1
    fi
    mkdir -p $ovirt/config
    # update local config using the one embedded in livecd image
    # TODO admin tool for adding /config into livecd image
    if [ -e $live/config/krb5.conf ]; then
      cp -a $live/config/krb5.conf $ovirt/config \
      || echo "krb5.conf copy failed"
    fi
    if [ -e $live/config/krb5.tab ]; then
      cp -a $live/config/krb5.tab $ovirt/config \
      || echo "krb5.tab copy failed"
    fi

    if [ $local_os = 0 ]; then
      # config update only, cleanup and continue booting
      umount $ovirt && rmdir $ovirt
      umount $live && rmdir $live
    else
      # install oVirt Node image for local boot
      if [ -e "$live/syslinux" ]; then
        syslinux=syslinux
      elif [ -e "$live/isolinux" ]; then
        syslinux=isolinux
      else
        syslinux=
      fi
      rm -rf $ovirt/boot
      rm -rf $ovirt/LiveOS
      mkdir -p $ovirt/boot/grub
      mkdir -p $ovirt/LiveOS
      cp -a $live/LiveOS/squashfs.img $ovirt/LiveOS \
      && cp -a $live/$syslinux/initrd0.img $ovirt/boot \
      && cp -a $live/$syslinux/vmlinuz0 $ovirt/boot
      if [ $? -ne 0 ]; then
        echo "image copy failed"; return 1
      fi
      part_num=$(( ${part#$disk} - 1 ))
      cat > $ovirt/boot/grub/grub.conf << EOF
default=0
timeout=2
hiddenmenu
title oVirt Managed Node
    root (hd0,$part_num)
    kernel /boot/vmlinuz0 ro root=LABEL=OVIRT roottypefs=ext2 liveimg $bootparams
    initrd /boot/initrd0.img
EOF
      grub-install --root-directory=$ovirt $disk >&2
      if [ $? -ne 0 ]; then
        echo "boot loader install failed"; return 1
      fi
      # remove 1.5 stages we don't need
      find $ovirt/boot/grub -name '*_stage1_5' ! -name e2fs_stage1_5 \
        -exec rm {} \;
      umount $ovirt && rmdir $ovirt
      umount $live && rmdir $live
      # FIXME avoid reboot loops
      # temp. workaround: sync and wait
      sync; sync; sync
      printf "oVirt local installation finished, press Enter to reboot." \
        > /dev/console
      read key
      if [ "$key" = "debug" ]; then
        sh > /dev/console 2>&1
      fi
      reboot
    fi
}

start() {
    # oVirt boot parameters
    #   BOOTIF=<MAC> (appended by pxelinux)
    #   ovirt_init=usb|scsi[:serial#]
    #   ovirt_local_boot

    #   BOOTIF=<MAC> (appended by pxelinux)
    # network boot interface is assumed to be on management network where
    # oVirt Server is reachable
    # IPAPPEND 2 in pxelinux.cfg appends MAC address of the booting node
    # e.g. BOOTIF=01-00-16-3e-12-34-57
    bootif=

    #   ovirt_init=usb|scsi[:serial#]
    # local installation target disk
    # usb|scsi - select disk type, as reported by udev ID_BUS
    # serial# - select exact disk using serial number, as reported by
    #           udev ID_SERIAL
    # e.g. ovirt_init=usb:Generic_STORAGE_DEVICE_0000145418-0:0
    target=

    #   ovirt_local_boot
    # install/update oVirt Node image on the local installation target disk
    local_os=0

    # save boot parameters like console= for local disk boot menu
    bootparams=

    for i in $(cat /proc/cmdline); do
        case $i in
            BOOTIF=??-??-??-??-??-??-??)
            i=${i#BOOTIF=??-}
            bootif=$(grep -il $(echo $i|sed 's/-/:/g') /sys/class/net/eth*/address|rev|cut -d/ -f2|rev)
            ;;
            ovirt_init*)
            target=${i#ovirt_init}
            if [ -z "$target" ]; then
              target='='
            fi
            ;;
            ovirt_local_boot*)
            local_os=1
            ;;
            console=*)
            bootparams="$bootparams $i"
            ;;
        esac
    done

    set -x
    local_install "$local_os" "$target" "$bootparams"
    set +x
    configure_from_network $bootif

    # find all of the partitions on the system

    # get the system pagesize
    PAGESIZE=`getconf PAGESIZE`

    # look first at raw partitions
    BLOCKDEVS=`ls /dev/sd? /dev/hd? 2>/dev/null`

    # now LVM partitions
    LVMDEVS="$DEVICES `lvscan | awk '{print $2}' | tr -d \"'\"`"

    SWAPDEVS="$LVMDEVS"
    for dev in $BLOCKDEVS; do
        SWAPDEVS="$SWAPDEVS `fdisk -l $dev 2>/dev/null | tr '*' ' ' \
                                     | awk '$5 ~ /82/ {print $1}'`"
    done

    # now check if any of these partitions are swap, and activate if so
    for device in $SWAPDEVS; do
        sig=`dd if=$device bs=1 count=10 skip=$(( $PAGESIZE - 10 )) \
            2>/dev/null`
        if [ "$sig" = "SWAPSPACE2" ]; then
            swapon $device
        fi
    done
}

case "$1" in
    start)
        printf "Starting ovirt-early: "

        {
            start
        } >> $OVIRT_LOGFILE 2>&1

        test $? == 0 && success || failure
        echo
        ;;
    *)
        echo "Usage: ovirt-early {start}"
        exit 2
esac
