#!/bin/sh
set -u # exit on using unset var

#        name = bootcdmk2diskconf - create bootcd2disk.conf for backup
#       autor = Bernd Schumacher 
#   startdate = 08.05.2007

# exclude this filesystems from RIDF (readinfo from df)
RIDF_EXCLUDES="-e ^/dev/shm.*/dev/shm"

# only grep filesystems from fstab
MOUNTGREP='grep -e "^/dev/" -e "^LABEL=" | grep -v -e "\<noauto\>" -e "\<auto\>" -e "\<swap\>"  -e "\<devpts\>" -e "\<sysfs\>" -e "\<tmpfs\>" -e "\<proc\>"'


######################## nothing to configure behind this line ###############

# The following parameters are defined in the order listed
PARAMETERS="ERRLOG
DISK0
LVMGRP
LVMVOL
TRYFIRST
SFDISK0
VFAT
EXT2FS
EXT3
SWAP
MOUNT
UMOUNT
FSTAB
GRUB2
GRUB
GRUBBOOTDISK
GRUBBOOTDIR
GRUBDEVICEMAP
LILO
ELILO
SSHHOSTKEY
UDEV_FIXNET
IMAGEURL
PARTITIONLABEL"

USAGE="
Usage: bootcdmk2diskconf [-v] [-d]
   -v 	verbose 
   -d   debug
   -s   same; same size for last partition. If this option is not used 
        the last partition will be adjusted to actual disk size.
   -e   exact; exact disks will be used. Normally only the disk order will
        be used.
   -b   This option is needed to create a bootcd2disk.conf that can be 
        used to restore an offline backup.
   -f <fstabdev>
        Not the actual running system is used, but a system described by 
	the fstab that can be found on device <fstabdev>.
	<fstabpath>. example: -f /dev/sde7
"

debug()
{
  if [ "$DEBUG" ]; then
    echo "DEBUG $*" >&2
  fi
}

# label2dev <label>
label2dev()
{
  mount -vnf LABEL=$1 | # -> /ram1/dev/sda1 on /mnt/boot type ext3 (rw)
    awk '{print $1}' | # -> /ram1/dev/sda1
    sed "s|/ram[^/]*||" # /dev/sda1
}


# function: labelsed -- remove labels by real devices
# get: stdin -- stream
labelsed()
{
  local T T1 L j i
  T="$(cat)"
  debug "labelsed INPUT=<$T>"
  L="$(echo "$T" | sed -n "s/.*LABEL=\([^:[:space:]]\+\).*/\1/p" | sort -n)"
  for i in $L; do
    debug "labelsed search for label <$i>"
    j="$(echo "$RILABEL" | grep ":$i$" | awk -F: '{print $1}')"
    debug "labelsed found <$j>"
    T1="$(echo "$T" | sed -e "s|LABEL=$i:|$j:|" -e "s|LABEL=$i$|$j|")"
    T="$T1"
  done
  debug "labelsed OUTPUT=<$T>"
  echo "$T"
}
#echo "   Precondition: RILABEL-Line1=\"/dev/sda1:/\" RILABEL-Line2=\"/dev/sda2:SWAP-sda2\""
#echo "          Input: Line1=\"LABEL=/:/\" Line2=\"LABEL=SWAP-sda2\""
#echo "       Expected: Line1=\"/dev/sda1:/\" Line2=\"/dev/sda2\""
#echo "labelsed Output:"
#echo "LABEL=/:/
#LABEL=SWAP-sda2" | DEBUG="" RILABEL="/dev/sda1:/
#/dev/sda2:SWAP-sda2" labelsed 
#exit 0


# rmvg()
rmvg()
{
  T="$(cat)"
  debug "rmvg INPUT=<$T>"
  for i in $CIVG; do
    debug "rmvg $i"
    T1="$(echo "$T" | grep -v -e "/dev/$i\>")"
    T="$T1"
  done
  debug "rmvg OUTPUT=<$T>"
  echo "$T"
}

# function: readinfo -- read informations of disks
readinfo()
{
  MNT=""
  [ $# -gt 0 ] && local MNT="$1"  

  RIFSTAB="$(cat $MNT/etc/fstab)"
  RIGRUB="$(for i in $MNT/boot/grub/menu.lst $MNT/boot/grub/grub.conf $MNT/boot/boot/grub/menu.lst \
    $MNT/boot/boot/grub/grub.conf; do if [ -s $i ]; then cat $i; break; fi; done)"
  RILV="$([ "$(type lvdisplay 2>/dev/null)" ] && 
    (lvdisplay --ignorelockingfailure --units m -C | tail -n +2 | 
    sed "s/^[[:space:]]*//" | awk '{printf("%s:%s:%d\n",$1,$2,$4)}'))"
  RIPV="$([ "$(pvdisplay --ignorelockingfailure 2>/dev/null)" ] && 
    (pvdisplay --ignorelockingfailure -C | tail -n +2 |
    awk '{printf("%s:%s\n",$1,$2)}'))"
  # read from df
  RIDF="$(df -lPm | grep -e "^/dev/" -e "^LABEL=" | grep -v $RIDF_EXCLUDES |
    awk '{printf("%s:%s\n",$1,$2)}')"
  RILABEL="$(for i in $(echo "$RIFSTAB" | 
    sed -n "s/.*LABEL=\([^[:space:]]\+\).*/\1/p" | sort -n); do
      echo "$(label2dev $i):$i"
    done)"
  RIPARTITION="$(cat /proc/partitions | tail -n +3|
    awk '{printf("/dev/%s:%d\n",$4,$3/1024)}')"
}

calcinfo()
{
  local i j

  CIVG="$(echo "$RILV" | awk -F: '{print $2}' | sort -u)" 

  CISWAP="$(echo "$RIFSTAB" | grep -e "^/dev/" -e "^LABEL=" | 
    grep "\<swap\>" |awk '{print $1}' | labelsed)"

  CIFS="$(echo "$RIFSTAB"| eval "$MOUNTGREP" | awk '{printf("%s:%s\n",$1,$2)}' | labelsed )"

  # add   space  for dash ;-) 
  CIPART="$( (echo "$CIFS" | awk -F: '{print $1}'; echo "$CISWAP"; 
    echo "$RIPV" | awk -F: '{print $1}') | rmvg | sort -u)"

  CIDISK="$(
    j=0
    for i in $(echo "$CIPART" | sed "s/p*[123456789][[:digit:]]*//"|uniq); do
      echo "$i:DISK$j"
      j=$(($j+1))
    done
  )"
}

showinfo()
{
  debug "RIFSTAB=\"$RIFSTAB\"" >&2
  debug "RIGRUB=\"$RIGRUB\"" >&2
  echo "RILV=\"$RILV\"" >&2
  echo "RIPV=\"$RIPV\"" >&2
  echo "RIDF=\"$RIDF\"" >&2
  echo "RILABEL=\"$RILABEL\"" >&2
  echo "RIPARTITION=\"$RIPARTITION\"" >&2

  echo "CIVG=\"$CIVG\"" >&2
  echo "CISWAP=\"$CISWAP\"" >&2
  echo "CIFS=\"$CIFS\"" >&2
  echo "CIPART=\"$CIPART\"" >&2
  echo "CIDISK=\"$CIDISK\"" >&2
}

parttrans()
{
  local i dev dsk

  T="$(cat)"
  for i in $CIDISK; do
    # i=/dev/sda:DISK1 -> dev=/dev/sda dsk=DISK1
    dev="$(echo "$i" | awk -F: '{print $1}')"
    dsk="$(echo "$i" | awk -F: '{print $2}')"

    T1="$(echo "$T" | sed "s|${dev}p*\>|$dsk|")"
    T="$(echo "$T1" | sed "s|${dev}p*\([[:digit:]]\+\)|${dsk}P\1|")"
  done
  echo "$T"
}

trans()
{
  echo "$1"  | sed "$PARTTRANS"
}

mapperlv2lv()
{
   T="$1"
   for i in $VG; do
     T1="$(echo "$T" | sed "s|/dev/mapper/$i-|/dev/$i/|")"
     T="$T1"
   done
   echo "$T"
}

template()
{
cat << END_OF_BOOTCD2DISK.CONF | tail -n +2 | head -n -1
### BOOTCD2DISK.CONF Placeholder ##############
END_OF_BOOTCD2DISK.CONF
}

checktemplate()
{
  USED="$(template | grep "^[^#[:blank:]]" | sed "s/\(.*\)=.*/\1/")"
  if [ "$USED" != "$PARAMETERS" ]; then
    echo "INTERNAL ERROR: <$PARAMETERS> != <$USED>" >&2
    exit 1
  fi
  exit 0
}

printbefore()
{
  ( template 
    cat <<END
LASTLINES="This is just to make printbefore work for the last lines"
END
  ) |
  awk '
    /^'$1'=/ {print B}
    /^[^#]/ && ! /^ *$/ {B=""}
    /^#/ || /^ *$/ { B=sprintf("%s\n%s",B,$0) }
  ' | 
  tail -n +2
}

#x=$(printbefore LVMGRP)
#x=$(printbefore LASTLINES)
#echo "x=<$x>"
#exit 0

print2diskconf()
{
  cat <<END
# bootcd2disk.conf - automatically created by bootcdmk2diskconf
END

  printbefore ERRLOG
  cat <<END
ERRLOG=/var/log/bootcd2disk.log
END

  printbefore DISK0

  for i in $CIDISK; do 
    # i=/dev/sda:DISK1 -> dev=/dev/sda dsk=DISK1
    dev="$(echo "$i" | awk -F: '{print $1}')"
    dsk="$(echo "$i" | awk -F: '{print $2}')"
    echo "ORIG_$dsk=\"$dev\""
  done

  for i in $CIDISK; do 
    # i=/dev/sda:DISK1 -> dev=/dev/sda dsk=DISK1
    dev="$(echo "$i" | awk -F: '{print $1}')"
    dsk="$(echo "$i" | awk -F: '{print $2}')"
    if [ "$EXACTDISKS" ]; then
      echo "$dsk=\"$dev\""
    else
      echo "$dsk=\"auto\""
    fi
  done

  printbefore LVMGRP
  echo "LVMGRP=\""
  for i in $RIPV; do
    echo "$(echo "$i" | awk -F: '{print $2}' 
      ) $(echo "$i" | awk -F: '{print $1}' | parttrans)"
  done
  echo "\""

  printbefore LVMVOL
  echo "LVMVOL=\""

  for i in $RILV; do
    echo "$(echo "$i" | awk -F: '{printf("%s %s %s\n", $1,$3,$2)}')"
  done
  echo "\""

  printbefore TRYFIRST
  cat <<END
TRYFIRST=""
END

  printbefore SFDISK0
  for i in $CIDISK; do                                 
    # i=/dev/cciss/c0d0:DISK0 -> x=/dev/cciss/c0d0 y=0
    x="$(echo "$i" | awk -F: '{print $1}')"                
    y="$(echo "$i" | awk -F: '{print $2}' | sed "s/DISK//")"  
    echo "SFDISK$y=\""
    # n is number of partition, pn is the size of partition n
    # m and pm are the same info for the following partition
    n=0
    while :; do
      # RIPARTITION="/dev/cciss/c0d0p1:101\n/dev/cciss/c0d0p2:34624" 
      # -> n=1, m=2, pn=101, pm=34624  -> n=2, m=3, pn=34624 pm=
      n=$(($n+1))
      m=$(($n+1))
      pn="$(echo "$RIPARTITION" | grep "^${x}p*${n}:" | awk -F: '{print $2}')"
      if [ ! "$pn" ]; then
        if [ "$n" = "1" ]; then
          pn=0 # if a disk is used as a whole we set size of partition1 to 0
          echo ",0"
          break
        else
          echo "bootcdmk2diskconf: FATAL Unknown Error" >&2
          exit 1
        fi
      fi
      if [ "$pn" -eq 0 -a "$n" -lt 5 ]; then
        echo ",,E"
        n=5 
	m=6
        pn="$(echo "$RIPARTITION" | grep "^${x}p*${n}:" | awk -F: '{print $2}')"
      fi
      pm="$(echo "$RIPARTITION" | grep "^${x}p*${m}:" | awk -F: '{print $2}')"
      if [ "$pm" ]; then
        echo ",$pn"
      else
        if [ "$SAMESIZE" ]; then
          echo ",$pn"
	else
          echo ";"
        fi
        break
      fi
    done
    echo "\""
  done

  printbefore VFAT
  cat <<END
VFAT=""
END

  printbefore EXT2FS
  echo "EXT2FS=\""
  echo "$CIFS" | awk -F: '{print $1}' | parttrans
  echo "\""

  printbefore EXT3
  cat <<END
EXT3="auto"
END

  printbefore SWAP
  echo "SWAP=\"$(echo "$CISWAP" | parttrans)\""

  # CIFS="<devpath>:<mountpoint>\n..."
  # Sort it with length of <mountpoint> for right mount order
  SORTCIFS=$(for i in $CIFS; do
    echo "$(echo "$i" | awk -F: '{print $2}'|wc -c):$i" | sort -n
  done)
  
  printbefore MOUNT

  echo "[ ! \"\$(set |grep \"^BOOTCDMP=\")\" ] && BOOTCDMP=\"/mnt\""

  echo "MOUNT=\""
  # SORTCIFS="<mountpointlen>:<devpath>:<mountpoint>\n..."
  for i in $SORTCIFS; do
    x="$(echo "$i" | awk -F: '{print $2}')"
    y="$(echo "$i" | awk -F: '{print $3}')"
    echo "mkdir -p \$BOOTCDMP$y; mount $x \$BOOTCDMP$y" | parttrans
  done
  echo "\""

  printbefore UMOUNT
  UMOUNT=""
  echo "UMOUNT=\""
  for i in $(echo "$SORTCIFS" | sort -nr); do
    x="$(echo "$i" | awk -F: '{print $3}')"
    echo "umount \$BOOTCDMP$x"
    UMOUNT="$UMOUNT umount \$BOOTCDMP$x;"
  done
  echo "\""
  
  if [ ! "$BACKUP" ]; then
    printbefore FSTAB
    echo "FSTAB=\""
    echo "$RIFSTAB" | parttrans
    echo "\""

    printbefore GRUB
    echo "GRUB=\""
    echo "$RIGRUB" | parttrans
    echo "\""
  else
    printbefore FSTAB
    echo "FSTAB=\"unchanged\""
    cat <<END
# FSTAB="unchanged". It is is used, as defined in backup. 
# But if you want to recreate it, for example because
# you restore on another hardware and hda changed to 
# sda try the following lines instead (see also GRUB):
END
    (
    echo "FSTAB=\""
    echo "$RIFSTAB" | parttrans
    echo "\""
    ) | sed "s/^/#/"
  
    printbefore GRUB
    echo "GRUB=\"unchanged\""
    cat <<END
# GRUB="unchanged". It is used, as definded in backup.
# But if you want to recreate it, try the following
# lines instead (see also FSTAB):
END
    (
    echo "GRUB=\""
    echo "$RIGRUB" | parttrans
    echo "\""
    ) | sed "s/^/#/"
  fi

  printbefore GRUBBOOTDISK
  cat <<END
GRUBBOOTDISK="hd0"
END

  printbefore GRUBBOOTDIR
  cat <<END
GRUBBOOTDIR="0"
END

  printbefore GRUBDEVICEMAP
  cat <<END
GRUBDEVICEMAP="auto"
END

  printbefore LILO
  cat <<END
LILO=""
END

  printbefore ELILO
  cat <<END
ELILO=""
END

  printbefore SSHHOSTKEY
  cat <<END
SSHHOSTKEY="unchanged"
# SSHHOSTKEY="unchanged". It is used, as definded in backup.
# But if you want to recreate it, use the following
# line instead:
#SSHHOSTKEY="yes"
END

  printbefore UDEV_FIXNET
  cat <<END
UDEV_FIXNET="no"
END

  printbefore IMAGEURL
  cat <<END
IMAGEURL=""
END

  if [ "$BACKUP" ]; then
    # default directory structure for backup called <name>:
    #   /<name>/cpio00
    #   /etc/bootcd/<name>/bootcd2disk.conf (this file)

    # echo "RESTORECMD=\"(cd /mnt; tar xzf /\\\$(basename \\\$CONFDIR)/backup.tgz)\""
    # echo "RESTORECMD=\"(cd /mnt; cat \\\$CONFDIR/backup* |gzip -d -c | cpio --null --quiet -idmn)\""
    echo "RESTORECMD=\"(cd \$BOOTCDMP; 
[ -f \\\$ISOLOOPBACK/backup/data/cpio00 ] && (cat \\\$ISOLOOPBACK/backup/data/cpio* |gzip -d -c | cpio --null --quiet -idmn);
[ -f \\\$ISOLOOPBACK/backup/data/star00 ] && (cat \\\$ISOLOOPBACK/backup/data/star* |gzip -d -c | star -U -xattr -x -f - );:
)\"" 
  fi

  printbefore PARTITIONLABEL
  echo "PARTITIONLABEL=\""
  echo "$RILABEL" | parttrans
  echo "\""

  printbefore LASTLINES
}


SAMESIZE=""
VERBOSE=""
DEBUG=""
EXACTDISKS=""
BACKUP=""
FSTABDEV=""
TESTDATA=""
while [ "$*" ]; do
  if [ "$1" = "-v" ]; then
    VERBOSE="1"
    shift
  elif [ "$1" = "-d" ]; then
    DEBUG="1"
    shift
  elif [ "$1" = "-s" ]; then
    SAMESIZE="$1"
    shift
  elif [ "$1" = "-e" ]; then
    EXACTDISKS="$1"
    shift
  elif [ "$1" = "-b" ]; then
    BACKUP="$1"
    shift 1
  elif [ "$1" = "-f" ]; then
    FSTABDEV="$2"
    shift 2
  elif [ "$1" = "-t" ]; then
    TESTDATA="$1"
    shift
  elif [ "$1" = "-checktemplate" ]; then
    # Internal Option to check if bootcd2disk.conf fits to this script
    checktemplate
    shift
  else
    echo "$USAGE" >&2
    exit 1
  fi
done

if [ "$TESTDATA" ]; then
  for i in RILV RIPV RIDF RILABEL RIPARTITION; do
    if [ ! "`set | grep ^$i=`" ]; then
      echo "TESTDATA $i not defined" >&2
      exit 1 
    fi
  done

elif [ "$FSTABDEV" ]; then

  if [ "$(/bin/ls /mnt)" ]; then
    echo "/mnt is not emply; please empty it, (rm or umount)" >&2
    exit 1
  fi
  mount $FSTABDEV /mnt 
  if [ $? -ne 0 ]; then
    echo "Problem mounting $FSTABDEV /mnt" >&2
    exit 1
  fi

  if [ ! "$(grep "# bootcdmk2diskconf entries" /etc/fstab)" ]; then
    echo "# bootcdmk2diskconf entries" >>/etc/fstab

    if [ -f /etc/fstab ]; then
      FSTABPATH="/etc/fstab"
    elif [ -f /fstab ]; then
      FSTABPATH="/fstab"
    else
      echo "Can not find fstab" >&2
      exit 1
    fi
    ADDMNT="$(cat /mnt/$FSTABPATH | eval "$MOUNTGREP" | awk '{$2="/mnt/"$2; print}')"
    [ "$VERBOSE" ] && echo "Additional mounts: <$ADDMNT>" >&2
    echo "$ADDMNT" >> /etc/fstab
  fi
  mount -a
  readinfo /mnt


else
  readinfo
fi

calcinfo
[ "$VERBOSE" ] && showinfo
print2diskconf


if [ "$FSTABDEV" ]; then

  cp /etc/fstab /etc/fstab.tmp
  cat /etc/fstab.tmp | awk 'BEGIN {p=1} /# bootcdmk2diskconf entries/ {p=0} {if(p) print}' >/etc/fstab
  rm /etc/fstab.tmp

  [ "$VERBOSE" ] && echo "Doing umount: <$UMOUNT>" >&2
  eval "$UMOUNT"

  if [ "$(/bin/ls /mnt)" ]; then
    echo "ERROR: /mnt is not emply; calculated umount <$UMOUNT> did not work !!!"
    exit 1
  fi

fi
