#!/bin/sh
# linbo_cmd - Backend worker script for LINBO
# (C) Klaus Knopper 2007-2010
#
# paedML/openML modifications by Thomas Schmitt
#
# ssd/4k/8k support - jonny@bzt.de 30.09.2012 alpha!
# ssd/4k/8k support - jonny@bzt.de 06.11.2012 anpassung fuer 2.0.12
#
# thomas@linuxmuster.net
# 20210127
# GPL v3
#

CLOOP_BLOCKSIZE="131072"
RSYNC_PERMISSIONS="--chmod=ug=rw,o=r"
UDEVADM="$(which udevadm)"

trap bailout 1 2 3 10 12 13 15

umask 002

PID="$$"

TMP="/tmp/linbo_cmd.$$.tmp"
rm -f "$TMP"

# Nur zum Debuggen
# echo "»linbo_cmd«" "»$@«"
ps w | grep linbo_cmd | grep -v grep >"$TMP"
if [ $(cat "$TMP" | wc -l) -gt 1 ]; then
  # echo "Possible Bug detected: linbo_cmd already running." >&2
  echo "Moeglicher Fehler erkannt: linbo_cmd laeuft bereits." >> /tmp/linbo.log
  #cat "$TMP" >&2
  cat "$TMP" >> /tmp/linbo.log
fi
rm -f "$TMP"
# EOF Debugging

# set terminal & PATH
export TERM=xterm
export PATH=/bin:/sbin:/usr/bin:/usr/sbin

printargs(){
  local arg
  local count=1
  for arg in "$@"; do
    echo -n "$((count++)): »$arg« "
  done
  echo ""
}

# test if variable is an integer
isinteger() {
  [ $# -eq 1 ] || return 1
  case $1 in
  *[!0-9]*|"") return 1;;
           *) return 0;;
  esac
}

# Is /cache writable?
# Displayed mount permissions may not be correct, do a write-test.
cache_writable(){
  local testfile="/cache/.write_test"
  [ -f "$testfile" ] && rm -f "$testfile" 2>/dev/null
  echo > "$testfile" 2>/dev/null
  local RC="$?"
  [ -f "$testfile" ] && rm -f "$testfile" 2>/dev/null
  return "$RC"
}

# check for forcegrub option
forcegrub(){
  case "$(cat /proc/cmdline)" in *\ forcegrub*) return 0;; esac
  return 1
}

# Check for "nonetwork" boot option or availability of linbo server
localmode(){
  case "$(cat /proc/cmdline)" in *\ nonetwork*|*\ localmode*) return 0;; esac
  [ -e /tmp/network.ok ] && return 1
  return 0
}

# upload logfile to linbo server or store it local in cache
# usage example (logfile parameter is optional): sendlog <name_of_logfile_in_tmp>
sendlog(){
  local RC="1"
  local logfiles="$1"
  [ -z "$logfiles" ] && logfiles="patch.log linbo.log image.log"
  local i
  for i in $logfiles; do
    # add log extension
    logfile="/tmp/$i"
    if [ -s "$logfile" ]; then
      if localmode; then
        if cache_writable; then
          echo "Speichere Logdatei $i im Cache."
          cp "$logfile" /cache
        fi
      else
        [ -e /tmp/linbo-network.done ] || return 0
        echo "Veranlasse Upload von $i."
        logfile="/tmp/$(hostname)_$i"
        rsync $(serverip)::linbo"$logfile" "/tmp/$i" 2>"$TMP" || true
      fi
    fi
  done
}

bailout(){
  echo "DEBUG: bailout() aufgerufen, linbo_cmd=$PID, my_pid=$$" >&2
  echo ""
  # Kill all processes that have our PID as PPID.
  local processes=""
  local names=""
  local pid=""
  local cmd=""
  local stat=""
  local relax=""
  local statfile=""
  for statfile in /proc/[1-9]*/stat; do
    while read pid cmd stat ppid relax; do
      if [ "$ppid" = "$PID" ]; then
        processes="$processes $pid"
        names="$names $cmd"
      fi
    done <"$statfile"
  done
  if [ -n "$processes" ]; then
    echo "Beende Prozesse: $processes $names" >&2
    kill $processes
    sleep 1
    echo ""
  fi
  cd /
  sync; sync; sleep 1
  umount /mnt >/dev/null 2>&1 || umount -l /mnt >/dev/null 2>&1
  sendlog
  umount /cache >/dev/null 2>&1 || umount -l /cache >/dev/null 2>&1
  umount /cloop >/dev/null 2>&1 || umount -l /cloop >/dev/null 2>&1
  rmmod cloop >/dev/null 2>&1
  rm -f "$TMP"
  echo "Abgebrochen." >&2
  echo "" >&2
  exit $?
}

interruptible(){
  local RC=0
  # "$@" >"$TMP" 2>&1 &
  "$@" &
  local newpid="$!"
  wait "$newpid"
  RC="$?"
  case "$RC" in
  0) true ;;
  2) kill "$newpid"; cd /; bailout 0 ;;
  #  *) [ -s "$TMP" ] && cat "$TMP" >&2 ;;
  esac
  return "$RC"
}

help(){
  echo "
  Ungueltiger LINBO-Befehl: »$@«

  Syntax: linbo_cmd command option1 option2 ...

  Beispiele:
  start bootdev rootdev kernel initrd append
               - Fahre Betriebssystem hoch
  syncr server  cachedev baseimage image bootdev rootdev kernel initrd
               - Synchronisiere Cache vom Server, dann Partitionen vom Cache
  syncl cachedev baseimage image bootdev rootdev kernel initrd
               - Synchronisiere Partitionen vom Cache

  Image-Arten:
  .cloop - Image vom kompletten Blockgeraet (block device, z.B. Partition), CLOOP-komprimiert
  .rsync - Differentielles RSYNC-Abbild, CLOOP-komprimiert
  " 1>&2
}

cmd="$1"
[ -n "$cmd" ] && shift # Command args are now $@

# fstype partition
fstype(){
  local phead="$(dd if="$1" bs=128k count=2 2>/dev/null)"
  local RC="$?"
  [ "$RC" = "0" ] || return "$RC"
  case "$phead" in
    *NTFS*) echo "ntfs" ;;
    # tschmitt: we also need to know fat
    *[Mm][Kk][Dd][Oo][Ss][Fs][Ss]*|*[Ff][Aa][Tt]*) echo "vfat" ;;
    *[Rr][Ee][Ii][Ss][Ee][Rr]*) echo "reiserfs" ;;
    *) echo "auto" ;;
  esac
  return 0
}

# tschmitt
# get DownloadType from start.conf
downloadtype(){
  local RET=""
  if [ -s /start.conf ]; then
    RET="$(grep -i ^downloadtype /start.conf | tail -1 | awk -F= '{ print $2 }' | awk '{ print $1 }' | tr A-Z a-z)"
    # get old option for compatibility issue
    if [ -z "$RET" ]; then
      RET="$(grep -i ^usemulticast /start.conf | tail -1 | awk -F= '{ print $2 }' | awk '{ print $1 }' | tr A-Z a-z)"
      [ "$RET" = "yes" ] && RET="multicast"
    fi
  fi
  echo "$RET"
}

# tschmitt
# fetch hostgroup from start.conf
hostgroup(){
  local hostgroup=""
  [ -s /start.conf ] || return 1
  hostgroup=`grep -i ^group /start.conf | tail -1 | awk -F= '{ print $2 }' | awk '{ print $1 }'`
  echo "$hostgroup"
}

# cachedev: prints cache device from start.conf
cachedev(){
  [ -s /start.conf ] || return 1
  grep -iw ^cache /start.conf | awk -F\# '{ print $1 }' | awk -F\= '{ print $2 }' | awk '{ print $1 }' | tail -1
}

# serverip: prints server ip from start.conf
serverip(){
  [ -s /start.conf ] || return 1
  grep -iw ^server /start.conf | awk -F\# '{ print $1 }' | awk -F\= '{ print $2 }' | awk '{ print $1 }' | tail -1
}

# tschmitt
# fetch osname from start.conf
# args: rootpartition
osname(){
  [ ! -b "$1" -o ! -s /start.conf ] && return 1
  local partition="$1"
  local osname
  local rootpart
  local value
  local line
  grep -iw ^[rn][oa][om][te] /start.conf | awk -F\# '{ print $1 }' | while read line; do
    value="$(echo "$line" | awk -F\= '{ print $2 }' | sed -e 's|^ *||')"
    case "$line" in
      [Nn][Aa][Mm][Ee]*) osname="$value" ;;
      [Rr][Oo][Oo][Tt]*) rootpart="$value" ;;
    esac
    if [ -n "$osname" -a -n "$rootpart" ]; then
      if [ "$rootpart" = "$partition" ]; then
        echo "$osname"
        return 0
      fi
      osname=""
      rootpart=""
    fi
  done
  return 1
}

# print kernel options from start.conf
kerneloptions(){
  [ -s /start.conf ] || return 1
  grep -i ^kerneloptions /start.conf | tail -1 | sed -e 's/#.*$//' -e 's/kerneloptions//I' | awk -F\= '{ print substr($0, index($0,$2)) }' | sed -e 's/ =//' -e 's/^ *//g' -e 's/ *$//g'
}

# fschuett
# fetch SystemType from start.conf
systemtype(){
  local systemtype="bios"
  [ -s /start.conf ] || return 1
  systemtype=`grep -iw ^SystemType /start.conf | tail -1 | awk -F= '{ print $2 }' | awk '{ print $1 }'`
  echo "$systemtype"
}

get_64(){
  local is_64=""
  uname -a | grep -q x86_64 && is_64="64"
  echo "$is_64"
}


# fschuett
# extract block device name for sd?,/dev/sd?,*blk?p?,/dev/*blk?p?
# get_disk_from_partition partition
get_disk_from_partition(){
  local p="$1"
  local disk=
  expr "$p" : ".*p[[:digit:]][[:digit:]]*" >/dev/null && disk=${p%%p[0-9]*}
  expr "$p" : ".*[hsv]d[[:alpha:]][[:digit:]][[:digit:]]*" >/dev/null && disk=${p%%[0-9]*}
  if [ -n "$disk" ]; then
    echo "$disk"
    return 0
  else
    echo "$1"
    return 1
  fi
}

# fschuett
# extract disk device names from start.conf partition definitions
# get_disks
get_disks_startconf(){
  [ -s /start.conf ] || return
  local parts="$(grep -iw ^dev /start.conf | awk -F\= '{ print $2 }' | awk '{ print $1 }' )"
  local disks=
  for p in $parts; do
    if [ -z "$disks" ]; then
      disks="$(get_disk_from_partition "$p")"
    else
      disks="$disks $(get_disk_from_partition "$p")"
    fi
  done;
  disks="$(echo $disks|tr " " "\n" | sort -u)"
  echo "$disks"
  return 0
}


# tschmitt
# get disks from kernel
get_disks(){
  local i
  local disks
  if [ -d /dev/disk/by-path ]; then
    for i in $(ls -l /dev/disk/by-path | grep -v '\-part' | awk -F\> '{ print $2 }' | awk -F\/ '{ print $3 }'); do
      if [ -z "$disks" ]; then
        disks="/dev/$i"
      else
        disks="$disks /dev/$i"
      fi
    done
  # Fallback if disk by-path does not exist. This is caused by missing udev rules for excample in nvme files
  else
     for i in $(ls -l /dev/disk/by-id | grep -v '\-part' | awk -F\> '{ print $2 }' | awk -F\/ '{ print $3 }'); do
       if [ -z "$disks" ]; then
        disks="/dev/$i"
       else
        disks="$disks /dev/$i"
       fi
     done
  fi
  echo "$disks"

  return 0
}


# tschmitt
# fetch fstype from start.conf
# fstype_startconf dev
fstype_startconf(){
  local dev="$1"
  local type=""
  local section=""
  local param=""
  local tdev=""
  local pfound=0
  local dfound=0
  local line=""
  if [ ! -e /start.conf ]; then
    echo "Fatal! start.conf nicht gefunden!"
    return 1
  fi
  while read line; do
    section=`echo $line | awk '{ print $1 }' | tr A-Z a-z`
    [ "$section" = "[partition]" ] && pfound=1
    if [ "$pfound" = 1 ]; then
      param=`echo $line | awk '{ print $1 }' | tr A-Z a-z`
      if [ "$param" = "dev" ]; then
        tdev=`echo $line | awk -F= '{ print $2 }' | awk '{ print $1 }'`
        if [ "$tdev" = "$dev" ]; then
          dfound=1
        else
          pfound=0; dfound=0; type=""
        fi
      fi
      if [ "$param" = "fstype" ]; then
        type=`echo $line | awk -F= '{ print $2 }' | awk '{ print $1 }' | tr A-Z a-z`
      fi
      if [ "$dfound" = 1 -a -n "$type" ]; then
        echo "$type"
        return 0
      fi
    fi
  done </start.conf
  return 1
}

# get partitionlabel from start.conf
# partlabel_startconf partition
partlabel_startconf(){
  [ -s /start.conf ] || return
  local part="$1"
  local line=""
  local dev=""
  local label=""
  grep -i ^[dl][ea][vb] /start.conf | awk -F\= '{ print $1 " " $2 }' | awk '{ print $1 " " $2 }' | while read line; do
    echo "$line" | grep -qi ^dev && dev="$(echo "$line" | awk '{ print $2 }')"
    echo "$line" | grep -qi ^label && label="$(echo "$line" | awk '{ print $2 }')"
    if [ -n "$dev" -a -n "$label" ]; then
      if [ "$dev" = "$part" ]; then
        echo "$label"
        return
      fi
      dev="" ; label=""
    fi
  done
}

# print_partlabel partition
print_partlabel(){
  [ -e /dev/disk/by-label ] || return
  local label="$(partlabel_startconf "$1")"
  [ -z "$label" ] && return
  ls -l /dev/disk/by-label/ | grep -qw "$label" && echo "$label"
}

# prints device name according to partition label
# print_realdev label
print_realdev(){
  [ -e /dev/disk/by-label ] || return
  local label="$1"
  [ -z "$label" ] && return
  local devname="$(ls -l /dev/disk/by-label/ | grep -w "$label" | awk -F\/ '{ print $3 }')"
  [ -n "$devname" ] && echo "/dev/$devname"
}

# label_allpartitions
label_allpartitions(){
  echo "Partitionslabel schreiben:" | tee -a /tmp/linbo.log
  if [ ! -e /dev/disk/by-uuid ]; then
    echo "Keine Partitionen gefunden!" | tee -a /tmp/linbo.log
    sendlog
    return 1
  fi
  local dev=""
  local line=""
  local label=""
  local RC=0
  ls -l /dev/disk/by-uuid/ | grep ^l | awk -F\/ '{ print $3 }' | sort -u | while read line; do
    dev="/dev/$line"
    label="$(partlabel_startconf "$dev")"
    if [ -n "$label" ]; then
      msg="Partition $dev --> Label $label ... "
      if mk_label "$dev" "$(fstype_startconf "$dev")" "$label" 2>&1 | tee -a /tmp/linbo.log; then
        echo "$msg OK!" | tee -a /tmp/linbo.log
      else
        echo "$msg FEHLER! Gemountet?" | tee -a /tmp/linbo.log
        RC=1
      fi
    fi
  done
  # issue 85
  [ -n "$UDEVADM" ] && "$UDEVADM" trigger
  [ -e /dev/disk/by-label ] || echo "Keine Partitions-Label gefunden!" | tee -a /tmp/linbo.log
  sendlog
  return "$RC"
}

# mountpart partition destdir [options]
mountpart(){
  local RC=0
  local type=""
  local i=0
  local wmsg=0
  local label="$(print_partlabel "$1")"
  # "noatime" is required for later remount, otherwise kernel will default to "relatime",
  # which busybox mount does not know
  local OPTS="noatime"
  if [ "$3" = "-r" ]; then OPTS="$OPTS,ro"; else OPTS="$OPTS,rw"; fi
  # fix vanished cloop symlink
  if [ "$1" = "/dev/cloop" ]; then
    # wait for cloop0 to appear
    for i in 1 2 3 4 5; do
      if [ -b /dev/cloop0 ]; then
        rm -f /dev/cloop
        ln -sf /dev/cloop0 /dev/cloop
        break
      else
        [ "$i" = "5" ] && break
        echo "CLOOP-Device ist noch nicht verfuegbar, versuche erneut..."
        wmsg=1
        sleep 2
      fi
    done
    if [ ! -b /dev/cloop0 ]; then
      echo "CLOOP-Device ist nicht bereit! Breche ab!"
      return 1
    else
      [ "$wmsg" = "1" ] && echo "...Ok! :-)"
    fi
  fi
  # wait for partition, if not labeled
  if [ -z "$label" ]; then
    for i in 1 2 3 4 5; do
      RC="$?"
      [ "$RC" = "0" ] && break
      [ "$i" = "5" ] && break
      echo "Partition $1 ist noch nicht verfuegbar, versuche erneut..."
      sleep 2
    done
    [ "$RC" = "0" ] || { echo "Partition $1 ist nicht verfuegbar, wurde die Platte schon partitioniert?" 1>&2; return "$RC"; }
    local mountdev="$1"
  else
    # mount per label
    local mountdev="LABEL=$label"
  fi
  type="$(fstype $1)"
  case "$type" in
    *ntfs*)
      OPTS="$OPTS,recover,remove_hiberfile,user_xattr,inherit,acl"
      #ntfs-3g "$1" "$2" -o "$OPTS" 2>/dev/null; RC="$?"
    ;;
    *fat*)
      #mount -o "$OPTS" "$1" "$2" ; RC="$?"
    ;;
    *)
      OPTS="$OPTS,acl,user_xattr"
      #mount -o "$OPTS" "$1" "$2" ; RC="$?"
    ;;
  esac
  mount -o "$OPTS" "$mountdev" "$2" ; RC="$?"
  # second try with default options and ntfsfix in case of ntfs (netzint)
  if [ "$RC" != "0" -a "$type" = "ntfs" ]; then
    echo "Windows partition seems in bad shape. Try ntfsfix to solve this issue."
    ntfsfix "$1"
    mount -o "$OPTS" "$mountdev" "$2" ; RC="$?"
    # second try with default options for other filesystems
  elif [ "$RC" != "0" ]; then
    mount "$mountdev" "$2" ; RC="$?"
  fi
  return "$RC"
}

# Return true if cache is NFS- or SAMBA-Share
remote_cache(){
  case "$1" in *:*|*//*|*\\*|*\\\\*) return 0 ;; esac
  return 1
}

# mklabel partition fstype label
mk_label(){
  local partition="$1"
  local fstype="$2"
  local label="$3"
  local RC=0
  case "$fstype" in
    swap) mkswap -L "$label" "$partition" || RC=1 ;;
    ext2|ext3|ext4) e2label "$partition" "$label" || RC=1 ;;
    [Nn][Tt][Ff][Ss]*) ntfslabel -f "$partition" "$label" || RC=1 ;;
    *[Ff][Aa][Tt]*) fatlabel "$partition" "$label" || RC=1 ;;
    *) return 1 ;;
  esac
  return $RC
}

# format partition fstype label
format(){
  #echo -n "format " ;  printargs "$@"
  local partition="$1"
  local fstype="$2"
  local label="$3"
  local fcmd
  local RC
  if [ -n "$label" ]; then
    case "$fstype" in
      swap|ext2|ext3|ext4|[Nn][Tt][Ff][Ss]*) label="-L $label" ;;
      reiserfs) label="-l $label" ;;
      *[Ff][Aa][Tt]*) label="-n $label" ;;
      *) ;;
    esac
  fi
  case "$fstype" in
    [Ss][Ww][Aa][Pp]) fcmd="mkswap $label $partition" ;;
    [Rr][Ee][Ii][Ss][Ee][Rr][Ff][Ss]) fcmd="mkreiserfs $label -f -f $partition" ;;
    [Ee][Xx][Tt][234]) fcmd="mkfs.$fstype -F $label $partition" ;;
    [Nn][Tt][Ff][Ss]) fcmd="mkfs.ntfs $label -Q $partition" ;;
    *[Ff][Aa][Tt]*) fcmd="mkdosfs $label -F 32 $partition" ;;
    *) return 1 ;;
  esac
  echo -n "Formatting $partition with $fstype ..."
  [ -d "$partition" ] || sleep 5
  $fcmd 2>> /tmp/linbo.log 1>> /tmp/linbo.log ; RC="$?"
  if [ "$RC" != "0" ]; then
    echo -n " Partition is not yet ready - trying again ..."
    sleep 2
    $fcmd 2>> /tmp/linbo.log 1>> /tmp/linbo.log ; RC="$?"
  fi
  if [ "$RC" = "0" ]; then
    echo " OK!"
    # issue 85
    [ -n "$label" -a -n "$UDEVADM" ] && "$UDEVADM" trigger
    # install linbo and grub in cache
    local cachedev="$(cachedev)"
    if [ "$cachedev" = "$partition" ]; then
      rm -f /tmp/.update.done
      rm -f /tmp/.grub-install
      rm -f /tmp/.prepare_grub
      update "$(serverip)" "$cachedev"
      mk_boot
      if mountcache "$cachedev"; then
        echo "Saving start.conf in cache."
        cp /start.conf /cache
        # save hostname for offline use
        if [ -s /tmp/network.ok ]; then
          source /tmp/network.ok
          local FQDN="${hostname}.${domain}"
          echo "Saving hostname $FQDN in cache."
          echo "$FQDN" > /cache/hostname
        fi
      fi
    fi
  else
    echo " Fehler!"
  fi
  return "$RC"
}

# mountcache partition [options]
mountcache(){
  local RC=1
  [ -n "$1" ] || return 1
  export CACHE_PARTITION="$1"
  # Avoid duplicate mounts by just preparing read/write mode
  local mount_opts="$(grep " /cache " /proc/mounts | awk '{ print $4 }')"
  if [ -n "$mount_opts" ]; then
    local RW=""
    echo "$mount_opts" | grep -q ".*rw.*" && RW="true"
    case "$2" in
      -r|-o\ *ro*) [ -n "$RW" ] && mount -o remount,ro /cache 2>> /tmp/linbo.log ; RC=0 ;;
      *) [ -n "$RW" ] || mount -o remount,rw /cache 2>> /tmp/linbo.log ; RC="$?" ;;
    esac
    return "$RC"
  fi
  case "$1" in
    *:*) # NFS
      local server="${1%%:*}"
      local dir="${1##*:}"
      echo "Mounte /cache per NFS von $1..."
      # -o nolock is EXTREMELY important here, otherwise mount.nfs will timeout waiting for
      # local portmap
      mount $2 -t nfs -o nolock,rsize=8192,wsize=8192,hard,intr "$1" /cache 2>> /tmp/linbo.log
      RC="$?"
      ;;
    \\\\*\\*|//*/*) # CIFS/SAMBA
      local server="${1%\\*}";  server="${server%/*}"; server="${server#\\\\}"; server="${server#//}"
      echo "Mounte /cache per SAMBA/CIFS von $1..."
      # unix extensions have to be disabled
      echo 0 > /proc/fs/cifs/LinuxExtensionsEnabled 2>/dev/null
      # mount.cifs (3) pays attention to $PASSWD
      # this does not work: $RSYNC_PASSWORD is not available and mount.cifs does not pay attention to $PASSWD
      #export PASSWD="$RSYNC_PASSWORD"
      #mount $2 -t cifs -o username=linbo,nolock "$1" /cache 2>/dev/null
      # temporary workaround for password
      [ -s /tmp/linbo.passwd ] && PASSWD="$(cat /tmp/linbo.passwd 2>/dev/null)"
      [ -z "$PASSWD" -a -s /tmp/rsyncd.secrets ] && PASSWD="$(grep ^linbo /tmp/rsyncd.secrets | awk -F\: '{ print $2 }' 2>/dev/null)"
      mount $2 -t cifs -o username=linbo,password="$PASSWD",nolock "$1" /cache 2>> /tmp/linbo.log
      RC="$?"
      if [ "$RC" != "0" ]; then
        echo "Zugriff auf $1 als Benutzer \"linbo\" mit Authentifizierung klappt nicht."
        mount $2 -t cifs -o nolock,guest,sec=none "$1" /cache 2>> /tmp/linbo.log
        RC="$?"
        if [ "$RC" != "0" ]; then
          echo "Zugriff als \"Gast\" klappt auch nicht."
        fi
      fi
      ;;
    /dev/*) # local cache
      echo "Mounte Cache-Partition $1 ..."
      mountpart "$1" /cache $2 2>> /tmp/linbo.log ; RC="$?"
      ;;
    *) # Yet unknown
      echo "Unbekannte Quelle fuer LINBO-Cache: $1" >&2
      ;;
  esac
  [ "$RC" = "0" ] || echo "Mounten von $1 nach /cache fehlgeschlagen!" >&2
  return "$RC"
}

killalltorrents(){
  local WAIT=5
  # check for running torrents and kill them if any
  if [ -n "`ps w | grep ctorrent | grep -v grep`" ]; then
    echo "Stoppe Torrents ..."
    killall -9 ctorrent 2>/dev/null
    sleep "$WAIT"
    [ -n "`ps w | grep ctorrent | grep -v grep`" ] && sleep "$WAIT"
  fi
}

# convert all units to MiB and ensure partability by 2048
convert_size(){
  local unit="$(echo $1 | sed 's|[^a-zA-Z]*||g')"
  local size="$(echo ${1/$unit} | awk -F\[,.] '{ print $1 }')"
  local unit="$(echo $unit | tr A-Z a-z | head -c1)"
  case "$unit" in
    k) size=$(( $size / 2048 * 2 )) ;;
    m) size=$(( $size / 2 * 2 )) ;;
    g) size=$(( $size * 1024 )) ;;
    t) size=$(( $size * 1024 * 1024 )) ;;
    *) return 1 ;;
  esac
  echo $size
}

# partition with parted, invoked by partition() for each disk
# args: table
mk_parted(){
  local table="$1"
  [ -s "$table" ] || return 1
  local disk="/dev/$(basename "$table")"
  [ -b "$disk" ] || return 1
  local lastnr="$(grep -c ^"$disk" "$table")"
  local dev
  local label
  local start
  local partstart
  local end
  local partend
  local extend
  local extpartend
  local size
  local unit="MiB"
  local id
  local fstype
  local partname
  local partflag
  local disklabel="msdos"
  local parttype="primary"
  local bootable
  local RC=0
  local CMD="parted -s -a opt $disk mkpart"
  # efi system -> gpt label
  systemtype | grep -qi efi && disklabel="gpt"
  echo "Creating new $disklabel partition table on $disk."
  # first overwrite mbr
  dd if=/dev/zero of=$disk bs=512 count=1 || RC="1"
  # second create partition label
  parted -s "$disk" mklabel "$disklabel" || RC="1"

  local n=0
  echo "partition label size id fstype bootable"
  while read dev label size id fstype bootable; do
    n=$(( n + 1 ))
    echo "$n: $dev $label $size $id $fstype $bootable"
    [ "$fstype" = "-" ] && fstype=""
    [ "$label" = "-" ] && label=""
    partname="" ; partflag=""

    # begin of first partition
    if [ $n -eq 1 ]; then
      start=1
    else
      if [ "$parttype" = "extended" -o "$parttype" = "logical" ]; then
        parttype="logical"
        # add 1 MiB to logical partition start position
        start=$(( $end + 1 ))
      else
        # start of next partition is the end of the partition before
        start=$end
      fi
    fi
    partstart=$start$unit

    # handle size if not set
    if [ "$size" = "-" ]; then
      partend="100%"
      extpartend="$partend"
    else
      isinteger "$size" && size="$size"k
      size="$(convert_size $size)"
      # don't increase the end counter in case of extended partition
      case "$id" in
        5|05) extend=$(( $start + $size )) ; extpartend=$extend$unit ;;
        * ) end=$(( $start + $size )) ; partend=$end$unit ;;
      esac
    fi

    # handle partition name
    if [ -n "$label" -a "$disklabel" = "gpt" ]; then
      partname="$label"
    else
      partname="$parttype"
    fi

    # handle last logical partition if size was not set and size for extended was set
    [ "$n" = "$lastnr" -a "$parttype" = "logical" -a "$partend" = "100%" -a -n "$extend" ] && partend=$extend$unit

    # create partitions
    case "$id" in
      c01|0c01) $CMD '"Microsoft reserved partition"' $partstart $partend || RC=1 ; partflag="msftres" ;;
      5|05)
        parttype="extended"
        $CMD $parttype $partstart $extpartend || RC=1
        if [ "$RC" = "0" ]; then
          # correct parted's idea of the extended partition id
          echo -e "t\n$n\n5\nw\n" | fdisk "$disk" 2>> /tmp/linbo.log 1>> /tmp/linbo.log || RC=1
        fi
        ;;
      6|06|e|0e) $CMD $partname fat16 $partstart $partend || RC=1 ;;
      7|07)
        if [ "$disklabel" = "gpt" ]; then
          $CMD '"Basic data partition"' NTFS $partstart $partend || RC=1
          partflag="msftdata"
        else
          $CMD $partname NTFS $partstart $partend || RC=1
        fi
        ;;
      b|0b|c|0c) $CMD $partname fat32 $partstart $partend || RC=1 ;;
      ef)
        if [ "$disklabel" = "gpt" ]; then
          $CMD '"EFI system partition"' fat32 $partstart $partend || RC=1
          partflag="boot"
        else
          $CMD $partname fat32 $partstart $partend || RC=1
          if [ "$RC" = "0" ]; then
            # correct parted's idea of the efi partition id on msdos disklabel
            echo -e "t\n$n\nef\nw\n" | fdisk "$disk" 2>> /tmp/linbo.log 1>> /tmp/linbo.log || RC=1
          fi
        fi
        ;;
      82) $CMD $partname linux-swap $partstart $partend || RC=1 ;;
      83) $CMD $partname $fstype $partstart $partend || RC=1 ;;
      *) $CMD $partname $partstart $partend || RC=1 ;;
    esac

    # set bootable flag
    if [ "$bootable" = "yes" ]; then
      if [ "$disklabel" = "msdos" ]; then
        echo -e "a\n$n\nw\n" | fdisk "$disk" 2>> /tmp/linbo.log 1>> /tmp/linbo.log || RC=1
      else
        # note: with gpt disklabel only one partition can own the bootable
        # flag, so the last one wins if multiple boot flags were set
        parted -s "$disk" set $n boot on || RC="1"
      fi
    fi

    # set other flags
    if [ -n "$partflag" ]; then
      parted -s "$disk" set $n $partflag on || RC="1"
    fi

    # format partition if NOFORMAT is not set
    if [ -z "$NOFORMAT" -a -n "$fstype" ]; then
      format "$dev" "$fstype" "$label" || RC="1"
    fi

  done < "$table"

  if [ "$RC" = "0" ]; then
    echo "Partitionierung von $disk erfolgreich beendet!"
  else
    echo "Partitionierung von $disk fehlerhaft! Details siehe $(hostname)_linbo.log."
  fi
  return "$RC"
}

# Changed: All partitions start on cylinder boundaries.
# partition dev1 size1 id1 bootable1 filesystem dev2 ...
# When "$NOFORMAT" is set, format is skipped, otherwise all
# partitions with known fstypes are formatted.
partition(){
  [ -s /start.conf ] || return 1
  killalltorrents
  #echo -n "partition " ;  printargs "$@"
  # umount /cache if mounted
  if cat /proc/mounts | grep -q /cache; then
    cd /
    if ! umount /cache &>/dev/null; then
      umount -l /cache &>/dev/null
      sleep "$WAIT"
      if cat /proc/mounts | grep -q /cache; then
        echo "Kann /cache nicht unmounten." >&2
        return 1
      fi
    fi
  fi

  # collect partition infos from start.conf and write them to table
  local dev
  local label
  local size
  local id
  local fstype
  local bootable
  local line
  local table="/tmp/partitions"
  local RC="0"
  rm -f "$table"
  grep -v '^$\|^\s*\#' /start.conf | awk -F\# '{ print $1 }' | sed -e 's| ||g' -e 's|[ \t]||' | tr A-Z a-z | while read line; do
    if echo "$line" | grep -q ^'\['; then
      if [ -n "$dev" ]; then
        [ -z "$label" ] && label="-"
        [ -z "$fstype" ] && fstype="-"
        [ -z "$size" ] && size="-"
        [ -z "$bootable" ] && bootable="-"
        echo "$dev $label $size $id $fstype $bootable" >> "$table"
      fi
      dev=""; label=""; id=""; fstype=""; size=""; bootable=""
      continue
    fi
    case "$line" in dev=*|label=*|id=*|fstype=*|size=*|bootable=*) eval "$line" ;; esac
  done

  # get all disks from start.conf
  local disks="$(get_disks_startconf)"
  local disk
  local diskname
  # sort table by disks and partitions
  for disk in $disks; do
    diskname="${disk#\/dev\/}"
    grep ^"$disk" "$table" | sort > "/tmp/$diskname"
    mk_parted "/tmp/$diskname" || RC="1"
  done
  rm -f /tmp/.update.done
  rm -f /tmp/.grub-install
  rm -f /tmp/.prepare_grub
  return "$RC"
}

# print efi partition
print_efipart(){
  # test for efi system
  [ -d /sys/firmware/efi ] || return 1
  [ -s /start.conf ] || return 1
  local dev
  local id
  local label
  local line
  grep -v '^$\|^\s*\#' /start.conf | awk -F\# '{ print $1 }' | sed -e 's| ||g' -e 's|[ \t]||' | tr A-Z a-z | while read line; do
    if echo "$line" | grep -q ^'\['; then
      if [ "$id" = "ef" ]; then
        echo "$dev"
        return 0
      fi
      dev=""; id=""; label=""
      continue
    fi
    case "$line" in dev=*|id=*|label=*) eval "$line" ;; esac
  done
}

# print_grubpart partition
print_grubpart(){
  local partition="$1"
  [ -b "$partition" ] || return 1
  local partnr="$(echo "$partition" | sed -e 's|/dev/[hsv]d[abcdefgh]||' -e 's|/dev/xvd[abcdefgh]||' -e 's|/dev/mmcblk[0-9]p||' -e 's|/dev/nvme0n[0-9]p||')"
  case "$partition" in
    /dev/mmcblk*) local disknr="$(echo "$partition" | sed 's|/dev/mmcblk\([0-9]\)p[1-9]|\1|')" ;;
    /dev/nvme0n*)
      local disknr="$(echo "$partition" | sed 's|/dev/nvme0n\([0-9]\)p[1-9]|\1|')"
      disknr=$(( $disknr - 1 ))
      ;;
    *)
      local ord="$(printf "$(echo $partition | sed 's|/dev/*[hsv]d\([a-z]\)[0-9]|\1|')" | od -A n -t d1)"
      local disknr=$(( $ord - 97 ))
      ;;
  esac
  echo "(hd${disknr},${partnr})"
}

# print efi bootnr of given item
# print_efi_bootnr item efiout
print_efi_bootnr(){
  local item="$1"
  local efiout="$2"
  [ -z "$item" ] && return 1
  if [ -s "$efiout" ]; then
    local bootnr="$(grep -iw "$item" "$efiout" | head -1 | awk -F\* '{ print $1 }' | sed 's|^Boot||')"
  else
    local bootnr="$(efibootmgr | grep -iw "$item" | head -1 | awk -F\* '{ print $1 }' | sed 's|^Boot||')"
  fi
  if [ -n "$bootnr" ]; then
    echo "$bootnr"
  else
    return 1
  fi
}

# create efi boot entry
# create_efiboot label efipart
create_efiboot(){
  # return if entry exists
  efibootmgr | grep ^Boot[0-9] | awk -F\* '{ print $2 }' | grep -qiw " $1" && return 0
  local label="$1"
  local efipart="$2"
  local efipartnr
  local efidisk="$(get_disk_from_partition "$efipart")"
  case "$efidisk" in
    *nvme*) efipartnr="$(echo "$efipart" | sed "s|${efidisk}p||")" ;;
    *) efipartnr="$(echo "$efipart" | sed "s|$efidisk||")" ;;
  esac
  local efiloader
  local bits
  case "$label" in
    *[Ww][Ii][Nn][Dd][Oo][Ww][Ss]*) efiloader="\\EFI\\Microsoft\\Boot\\bootmgfw.efi" ;;
    grub)
      bits="$(get_64)"
      [ -z "$bits" ] && bits="32"
      efiloader="\\EFI\\grub\\grubx${bits}.efi"
      ;;
    *) return 1 ;;
  esac
  efibootmgr --create --disk "$efidisk" --part "$efipartnr" --loader "$efiloader" --label "$label" 2>> /tmp/linbo.log 1>> /tmp/linbo.log || return 1
}

# set_efibootnext: creates efi bootnext entry
# args: bootloaderid
set_efibootnext(){
  local bootloaderid="$1"
  forcegrub && bootloaderid="grub"
  # get the bootnr
  local bootnextnr="$(print_efi_bootnr "$bootloaderid")"
  if [ -n "$bootnextnr" ]; then
    echo "Setze EFI BootNext: $bootnextnr."
    efibootmgr --bootnext "$bootnextnr" 2>> /tmp/linbo.log 1>> /tmp/linbo.log || return 1
  else
    return 1
  fi
}

# set_efibootorder: set the boot order to local,network
set_efibootorder(){
  local efiout="/tmp/efiout"
  efibootmgr | grep ^Boot | grep -vi ipv6 > "$efiout"
  local i
  local nr
  local bootorder
  # fix bootnext in case of KVM virtualization
  if dmesg | grep -qw KVM; then
    local bootnextnr="$(grep -i ^bootnext "$efiout" | awk '{ print $2 }')"
    [ -n "$bootnextnr" ] && bootorder="$bootnextnr"
  fi
  # searchstrings for grub (local boot) and efi network devices
  for i in grub ipv4 pxev4 "efi network" "usb nic" "onboard nic"; do
    nr="$(print_efi_bootnr "$i" "$efiout")"
    [ -z "$nr" ] && continue
    if [ -n "$bootorder" ]; then
      bootorder="$bootorder,$nr"
    else
      bootorder="$nr"
    fi
  done
  if [ -n "$bootorder" ]; then
    echo "Setze EFI Bootreihenfolge: $bootorder."
    efibootmgr --bootorder "$bootorder" 2>> /tmp/linbo.log 1>> /tmp/linbo.log || return 1
  fi
}

# repair_efi: sets efi configuration into a proper state
# args: efipart
repair_efi(){
  local doneflag="/tmp/.repair_efi"
  [ -e "$doneflag" ] && return 0
  local efipart="$1"
  local efiout="/tmp/efiout"
  local startflag="/tmp/.start"
  local line
  local item
  local FOUND
  local bootnr
  # first remove redundant entries, keep entries with lower number
  efibootmgr | grep ^Boot[0-9] | sort -r > "$efiout" || return 1
  # read in the unique boot entries and test for multiple occurances of the same item
  awk -F\* '{ print $2 }' "$efiout" | sort -u | while read item; do
    [ -z "$item" ] && continue
    line=""
    FOUND=""
    # delete redundant entries
    grep "$item" "$efiout" | while read line; do
      if [ -z "$FOUND" ]; then
        FOUND="yes"
        continue
      else
        bootnr="$(echo "$line" | awk -F\* '{ print $1 }' | sed 's|Boot||')"
        efibootmgr --bootnum "$bootnr" --delete-bootnum 2>> /tmp/linbo.log 1>> /tmp/linbo.log || return 1
      fi
    done
  done
  # create grub entry if missing
  create_efiboot grub "$efipart" || return 1
  # set bootorder
  if [ ! -e "$startflag" ]; then
    set_efibootorder || return 1
  fi
  touch "$doneflag"
}

# write_devicemap devicemap
# write grub device.map file
write_devicemap() {
  [ -z "$1" ] && return 1
  local devicemap="$1"
  local disk
  local n=0
  rm -f "$devicemap"
  # iterate through all disks found by kernel
  for disk in $(get_disks); do
    echo "(hd${n}) $disk" >> "$devicemap"
    n=$(( $n + 1 ))
  done
  [ -s "$devicemap" ] || return 1
}

# umount_boot
# unmounts boot partition
umount_boot(){
  local i
  for i in /boot/efi /boot; do
    if mount | grep -q " $i "; then
      umount "$i" || umount -l "$i"
    fi
  done
}

# mount_boot efipart
# fake mounts boot partition for grub
mount_boot(){
  local efipart="$1"
  mkdir -p /boot
  if ! mount | grep -q " /boot "; then
    mount --bind /cache/boot /boot || return 1
  fi
  if [ -n "$efipart" ]; then
    mkdir -p /boot/efi
    if ! mount | grep -q " /boot/efi "; then
      if ! mount "$efipart" /boot/efi; then
        umount_boot
        return 1
      fi
    fi
    # fschuett, this can fail, if mount is ro
    if ! mkdir -p /boot/efi/EFI; then
      umount_boot
      return 1
    fi
  fi
}

# mk_winefiboot: restore and install windows efi boot files
# args: partition efipart bootloaderid
mk_winefiboot(){
  local partition="$1"
  local doneflag="/tmp/.mk_winefiboot.$(basename "$partition")"
  [ -e "$doneflag" ] && return 0
  local efipart="$2"
  local bootloaderid="$3"
  local RC="0"
  local win_bootdir="$(ls -d /mnt/[Ee][Ff][Ii]/[Mm][Ii][Cc][Rr][Oo][Ss][Oo][Ff][Tt]/[Bb][Oo][Oo][Tt] 2> /dev/null)"
  # restore bcd from old bios boot dir
  if [ -n "$win_bootdir" ]; then
    # restore bcd and efiboot files on efi partition
    local win_bcd="$(ls "$win_bootdir"/[Bb][Cc][Dd] 2> /dev/null)"
    if [ -n "$win_bcd" ]; then
      local win_efidir="$(ls -d /boot/efi/[Ee][Ff][Ii]/[Mm][Ii][Cc][Rr][Oo][Ss][Oo][Ff][Tt]/[Bb][Oo][Oo][Tt] 2> /dev/null)"
      if [ -z "$win_efidir" ]; then
        win_efidir="/boot/efi/EFI/Microsoft/Boot"
        mkdir -p "$win_efidir"
      fi
      # copy whole windows efi stuff to efi partition
      echo "Stelle Windows-Bootdateien auf EFI-Partition wieder her."
      rsync -r "$win_bootdir/" "$win_efidir/"
    fi
  else
    echo "Kann Windows-EFI-Bootdateien nicht restaurieren."
    RC="1"
  fi
  # create efi bootloader entry if missing
  create_efiboot "$bootloaderid" "$efipart" || RC="1"
  [ "$RC" = "0" ] && touch "$doneflag"
  return "$RC"
}

# mk_linefiboot: prepares linux os for efi boot
# args: partition grubdisk efipart bootloaderid
mk_linefiboot(){
  [ -d /mnt/boot/grub ] || return 1
  local partition="$1"
  local doneflag="/tmp/.mk_linefiboot.$(basename "$partition")"
  [ -e "$doneflag" ] && return 0
  local grubdisk="$2"
  local efipart="$3"
  local bootloaderid="$4"
  local RC="0"
  mkdir -p /mnt/boot/efi
  mount "$efipart" /mnt/boot/efi || return 1
  mkdir -p /mnt/boot/efi/EFI
  grub-install --root-directory=/mnt --bootloader-id="$bootloaderid" "$grubdisk" 2>> /tmp/linbo.log || RC="1"
  umount /mnt/boot/efi
  [ "$RC" = "0" ] && touch "$doneflag"
  return "$RC"
}

# mk_efiboot: if returns 1 a reboot via grub will be initiated, othwerwise reboot via efi directly
# args: efipart partition grubdisk
mk_efiboot(){
  local efipart="$1"
  local partition="$2"
  local grubdisk="$3"
  local startflag="/tmp/.start"
  local bootloaderid
  local doneflag="/tmp/.grub-install"
  # repare efi configuration
  repair_efi "$efipart" || return 1
  # restore windows efi boot files
  local RC="0"
  if [ "$(fstype $partition)" = "ntfs" ]; then
    bootloaderid="Windows Boot Manager"
    mk_winefiboot "$partition" "$efipart" "$bootloaderid" || RC="1"
  else # assume linux system
    bootloaderid="$(osname "$partition")"
    if [ -n "$bootloaderid" ]; then
      mk_linefiboot "$partition" "$grubdisk" "$efipart" "$bootloaderid" || RC="1"
    fi
  fi
  # install default efi boot file
  local grubefi="/boot/efi/EFI/grub/grubx64.efi"
  if [ -s "$grubefi" ]; then
    echo "Stelle EFI-Standardboot wieder her."
    local efibootdir="$(ls -d /boot/efi/EFI/B[Oo][Oo][Tt] 2>/dev/null)"
    [ -z "$efibootdir" ] && efibootdir="/boot/efi/EFI/BOOT"
    local bootefi="$(ls $efibootdir/[Bb][Oo][Oo][Tt][Xx]64.[Ee][Ff][Ii] 2>/dev/null)"
    [ -z "$bootefi" ] && bootefi="$efibootdir/BOOTX64.EFI"
    mkdir -p "$efibootdir"
    rsync "$grubefi" "$bootefi" || RC="1"
  fi
  # set efi bootnext entry if invoked by start()
  if [ -e "$startflag" -a -n "$bootloaderid" ]; then
    set_efibootnext "$bootloaderid" || RC="1"
    # set bootorder
    set_efibootorder || RC="1"
    # cause another grub-install
    [ "$RC" = "0" ] && rm -f "$doneflag"
  fi
  [ "$RC" = "1" ] && echo "Fehler beim Schreiben der EFI-Boot-Konfiguration."
  # force grub reboot id kerneloption is set
  forcegrub && RC="1"
  return "$RC"
}

# mk_grubboot partition grubenv kernel initrd append
# prepare for grub boot after reboot
mk_grubboot(){
  local partition="$1"
  local grubenv="$2"
  local KERNEL="$3"
  [ -z "$KERNEL" ] && return 0
  local doneflag="/tmp/.mk_grubboot.$(basename "$partition")"
  [ -e "$doneflag" ] && return 0
  local INITRD="$4"
  local APPEND="$5"
  local RC="0"
  # reboot partition is the partition where the os is installed
  local REBOOT="$(print_grubpart $partition)"
  local LABEL="$(print_partlabel $partition)"
  # save reboot informations in grubenv
  echo "Schreibe Reboot-Informationen nach $grubenv."
  grub-editenv "$grubenv" set reboot_grub="$REBOOT" || RC="1"
  grub-editenv "$grubenv" set reboot_label="$LABEL" || RC="1"
  if [ "$KERNEL" != "[Aa][Uu][Tt][Oo]" ]; then
    [ "${KERNEL:0:1}" = "/" ] || KERNEL="/$KERNEL"
    grub-editenv "$grubenv" set reboot_kernel="$KERNEL" || RC="1"
    if [ -n "$INITRD" ]; then
      [ "${INITRD:0:1}" = "/" ] || INITRD="/$INITRD"
      grub-editenv "$grubenv" set reboot_initrd="$INITRD" || RC="1"
    fi
    if [ -n "$APPEND" ]; then
      grub-editenv "$grubenv" set reboot_append="$APPEND" || RC="1"
    fi
  fi
  [ "$RC" = "0" ] && touch "$doneflag"
  return "$RC"
}

# prepare_reboot: prepares filesystem for reboot to os
# args: grubdisk partition grubenv kernel initrd append efipart
prepare_reboot(){
  local grubdisk="$1"
  local partition="$2"
  local grubenv="$3"
  local KERNEL="${4#/}"
  local INITRD="${5#/}"
  local APPEND="$6"
  local efipart="$7"
  local efiboot="false"
  local noefibootmgr="$(kerneloptions | grep -iw noefibootmgr)"
  remote_cache "$(cachedev)" || local localcache="yes"
  if [ -z "$noefibootmgr" -a -n "$efipart" ]; then
    mk_efiboot "$efipart" "$partition" "$grubdisk" && efiboot="true"
  fi
  if [ "$efiboot" = "false" ]; then
    if [ -n "$localcache" ]; then
      mk_grubboot "$partition" "$grubenv" "$KERNEL" "$INITRD" "$APPEND" || return 1
    else
      # create reboot grubenv file on server
      local rebootstr="$(print_grubpart $partition)#${KERNEL}#${INITRD}#${APPEND}#.reboot"
      rsync $(serverip)::linbo/"$rebootstr" /tmp 2>"$TMP" || true
    fi
  fi
}

# prepare_grub: install and reset grub files in cache
# args: grubdir grubenv grubsharedir
prepare_grub(){
  local doneflag="/tmp/.prepare_grub"
  [ -e "$doneflag" ] && return 0
  echo "Providing grub environment in cache ..."
  local grubdir="$1"
  local grubenv="$2"
  local grubsharedir="$3"
  local RC=0
  [ -e "$grubdir" ] || mkdir -p "$grubdir"
  # write grub device.map file
  #echo -n " * Writing device.map ... "
  write_devicemap "$grubdir/device.map" || RC=1
  #echo "Ok!"
  # provide default grub.cfg with current append params on localmode
  if localmode; then
    echo -n " * Providing grub environment in localmode ... "
    local kopts="$(kerneloptions)"
    [ -z "$kopts" ] && kopts="splash quiet localboot"
    sed -e "s|linux \$linbo_kernel .*|linux \$linbo_kernel $(kerneloptions) localboot|g" "$grubsharedir/grub.cfg" > "$grubdir/grub.cfg" || RC=1
    echo "Ok!"
  fi
  # provide unicode font
  echo " * Providing grub environment ... "
  rsync "$grubsharedir/unicode.pf2" "$grubdir/unicode.pf2" || RC=1
  #echo "Ok!"
  # provide menu background image (obsolete)
  #[ -l "$grubdir/linbo_wallpaper.png" ] || rm -f "$grubdir/linbo_wallpaper.png"
  #ln -sf ../icons/linbo_wallpaper.png "$grubdir/linbo_wallpaper.png" || RC=1
  #rsync /icons/linbo_wallpaper.png "$grubdir/linbo_wallpaper.png" || return 1
  #echo "Ok!"
  # reset grubenv
  if [ -s "$grubenv" ]; then
    for i in reboot reboot_kernel reboot_initrd reboot_append; do
      grub-editenv "$grubenv" unset "$i" || RC="1"
    done
  else
    grub-editenv "$grubenv" create || RC="1"
  fi
  #echo "Ok!"
  [ "$RC" = "0" ] && touch "$doneflag"
  return "$RC"
}

# mk_boot: configure boot stuff
# args: partition kernel initrd append
mk_boot(){
  local KERNEL="${2#/}"
  local INITRD="${3#/}"
  local APPEND="$4"
  local efipart="$(print_efipart)"
  local partition="$1"
  local nonv
  remote_cache "$(cachedev)" || local localcache="yes"
  # get disk for grub install, use always the first disk from start.conf
  #local grubdisk="$(get_disks | head -1)"
  local grubdisk="$(get_disk_from_partition $(grep -iw ^dev /start.conf | awk -F\= '{ print $2 }' | awk '{ print $1 }' | grep ^/dev | sort -u | head -1))"
  if [ ! -b "$grubdisk" ]; then
    echo "$grubdisk ist kein Blockdevice!"
    return 1
  fi
  # mount boot partitions
  mount_boot "$efipart" || return 1
  # needed grub dirs
  local grubdir="/cache/boot/grub"
  local grubenv="$grubdir/grubenv"
  local grubsharedir="/usr/share/grub"
  local doneflag="/tmp/.grub-install"
  local RC="0"
  # prepare grub stuff
  if [ -n "$localcache" ]; then
    prepare_grub "$grubdir" "$grubenv" "$grubsharedir" || RC="1"
  fi
  # prepare reboot stuff
  if [ -n "$partition" ]; then
    prepare_reboot "$grubdisk" "$partition" "$grubenv" "$KERNEL" "$INITRD" "$APPEND" "$efipart" || RC="1"
  fi
  # install grub in mbr/efi
  if [ ! -e "$doneflag" -a -n "$localcache" ]; then
    echo -n "Installiere GRUB in MBR/EFI von $grubdisk ... "
    [ -n "$efipart" ] && nonv="--no-nvram"
    grub-install -s $nonv $grubdisk 2>> /tmp/linbo.log || RC="1"
    if [ "$RC" = "0" ]; then
      touch "$doneflag"
      echo "OK!"
    else
      echo "Fehler!"
    fi
  fi
  # umount boot partitions if mounted
  umount_boot
  return "$RC"
}

# download server file [important]
download(){
  local RC=1
  [ -n "$3" ] && echo "RSYNC Download $1 -> $2..."
  rm -f "$TMP"
  interruptible rsync -HaLz --partial "$1::linbo/$2" "$(basename $2)" 2>"$TMP"; RC="$?"
  if [ "$RC" != "0" ]; then
    # Delete incomplete/defective/non-existent file (maybe we should check for returncode=23 first?)
    rm -f "$2" 2>/dev/null
    if [ -n "$3" ]; then
      # Verbose error message if file was important
      cat "$TMP" >&2
      echo "Datei $2 konnte nicht heruntergeladen werden." >&2
    fi
  fi
  rm -f "$TMP"
  return "$RC"
}

# request macct file to invoke samba password hash ldap upload stuff on the server
invoke_macct(){
  local serverip="$(grep -m1 ^linbo_server= /tmp/dhcp.log | awk -F\' '{ print $2 }')"
  [ -z "$serverip" ] && return
  [ -s /mnt/.linbo ] || return
  local image="$(cat /mnt/.linbo)"
  local macctfile
  if [ -e "/cache/${image}.rsync" ]; then
    macctfile="${image}.rsync.macct"
  elif [ -e "/cache/${image}.cloop" ]; then
    macctfile="${image}.cloop.macct"
  else
    return
  fi
  echo "Setting machine password on $serverip ..."
  download "$serverip" "$macctfile"
  remote_cache "$(cachedev)" || rm -f "/cache/$macctfile"
}

# do_machinepw (not used)
# no args
# handle machine password stuff locally and on the server
do_machinepw(){
  local mpwfile="$(uuidgen).mpw"
  download "$(serverip)" "$mpwfile"
  local RC="0"
  if [ -s "$mpwfile" ]; then
    local machinepw="$(cat $mpwfile)"
    local srcdir="/linuxmuster-win"
    local tgtdir="/mnt$srcdir"
    sed -e "s|@@machinepw@@|$machinepw|" "$srcdir/set_machinepw.cmd.tpl" > "$tgtdir/set_machinepw.cmd" || RC="1"
    if [ "$RC" = "0" ]; then
      cp "$srcdir/lsaSecretStore.exe" "$tgtdir" || RC="1"
    fi
    [ "$RC" = "0" ] && echo "Maschinenpasswort wurde gesetzt."
  else
    RC="1"
  fi
  rm -f "$mpwfile"
  return "$RC"
}

# update linuxmuster-win scripts and install start tasks
# update_win partition
# invoked by start() & syncl()
update_win(){
  local doneflag="/tmp/.update_win"
  [ -e "$doneflag" ] && return 0
  [ -b "$1" ] || return 1
  local RC="0"
  mkdir -p /mnt/linuxmuster-win
  # copy scripts to os rootdir
  rsync -r /cache/linuxmuster-win/ /mnt/linuxmuster-win/ || RC="1"
  if [ "$RC" = "0" ]; then
    touch "$doneflag"
    # restrict access rights on certain files and folders on windows partition
    local partition="$1"
    local seclist
    local i
    local curdir="$(pwd)"
    cd /mnt
    for i in .linbo .guid.* Boot EFI linuxmuster-win; do
      [ -e "$i" ] && seclist="$seclist $i"
    done
    cd "$curdir"
    if [ -n "$seclist" ]; then
      sync
      umount /mnt 2>> /tmp/linbo.log
      for i in $seclist; do
        echo "Restrict access rights on $i:" | tee -a /tmp/linbo.log
        ntfssecaudit "$partition" 700 "$i" | tee -a /tmp/linbo.log
      done
      mountpart "$partition" /mnt 2>> /tmp/linbo.log
    fi
  fi
  return "$RC"
}

# start: start operating system
# args: boot root kernel initrd append cache
start(){
  echo -n "start " ;  printargs "$@"
  # if no kernel is given, do not start
  if [ -z "$3" ]; then
    echo "Nichts zu starten!"
    return 0
  fi
  local INITRD
  local APPEND
  local KERNEL="${3#/}"
  local i
  local partition="$2"
  local label="$(print_partlabel "$partition")"
  local cachedev="$6"
  local startflag="/tmp/.start"
  touch "$startflag"
  if mountpart "$partition" /mnt -w 2>> /tmp/linbo.log; then
    if [ -e "/mnt/$KERNEL" ]; then
      echo "Kernel $KERNEL auf Partition $partition gefunden."
      INITRD="${4#/}"
      APPEND="$5"
      # add root to append string
      if [ -n "$label" ]; then
        APPEND="root=LABEL=$label $APPEND"
      else
        APPEND="root=$partition $APPEND"
      fi
    else
      echo "Kernel $KERNEL auf Partition $partition nicht vorhanden. Setze auf \"auto\"."
      KERNEL="auto"
    fi
    if mountcache "$cachedev"; then
      # install/update grub/efi stuff if cache is mounted
      mk_boot "$partition" "$KERNEL" "$INITRD" "$APPEND" | tee -a /tmp/linbo.log
      # update linuxmuster-win scripts and install start tasks
      [ "$(fstype "$partition")" = "ntfs" -a -d /cache/linuxmuster-win ] && update_win "$partition" | tee -a /tmp/linbo.log
    fi
  else
    echo "Konnte Betriebssystem-Partition $partition nicht mounten." >&2
    umount /mnt 2>> /tmp/linbo.log
    mountcache "$cachedev" -r
    return 1
  fi
  # sets machine password on server
  invoke_macct
  # kill torrents if any
  killalltorrents
  sync
  umount /mnt 2>> /tmp/linbo.log
  sendlog
  # reboot to operating system
  reboot -f
}

# return partition size in kilobytes
# arg: partition
get_partition_size(){
  local part="$1"
  # fix wrong invokation by linbo_gui
  [ "$part" = "/dev/mmcblkp" ] && part="/dev/mmcblk0"
  [ "$part" = "/dev/nvmenp" ] && part="/dev/nvme0n1"
  # fix vanished cloop symlink
  if [ "$part" = "/dev/cloop" ]; then
    [ -e "/dev/cloop" ] || ln -sf /dev/cloop0 /dev/cloop
    part="/dev/cloop0"
  elif [ -L "$part" ]; then
    local partsrc="$(stat "$part" | grep File | awk -F\' '{ print $4}')"
    if ! echo "$partsrc" | grep -q '/dev/'; then
      partsrc="/dev/$partsrc"
    fi
    [ -b "$partsrc" ] && part="$partsrc"
  fi
  local size="$(grep -w "${part#\/dev\/}" /proc/partitions | awk '{ print $3 }' 2>> /tmp/linbo.log)"
  if [ -n "$size" ]; then
    echo "$size"
    return 0
  else
    return 1
  fi
}

# echo file size in bytes
get_filesize(){
  ls -l "$1" 2>/dev/null | awk '{print $5}' 2>/dev/null
  return $?
}

# save_efi_bcd targetdir efipart
# saves the windows efi file to os partition
save_efi_bcd(){
  local targetdir="$1"
  local efipart="$2"
  local efimnt="/cache/boot/efi"
  mkdir -p "$efimnt"
  mount "$efipart" "$efimnt" || return 1
  local sourcedir="$(ls -d "$efimnt"/[Ee][Ff][Ii]/[Mm][Ii][Cc][Rr][Oo][Ss][Oo][Ff][Tt]/[Bb][Oo][Oo][Tt] 2> /dev/null)"
  if [ -z "$sourcedir" ]; then
    echo "$sourcedir nicht vorhanden. Kann Windows-EFI-Bootdateien nicht nach $targetdir kopieren."
    umount "$efimnt" || umount -l "$efimnt"
    return 1
  fi
  echo "Kopiere Windows-EFI-Bootdateien von $sourcedir nach $targetdir."
  mkdir -p "$targetdir"
  local RC=0
  rsync -r "$sourcedir/" "$targetdir/" || RC="1"
  umount "$efimnt" || umount -l "$efimnt"
  [ "$RC" = "1" ] && echo "Fehler beim Kopieren der Windows-EFI-Bootdateien."
  return "$RC"
}

# print_guid partition
# print gpt uuid of a partition (works only with efi)
print_guid(){
  local partition="$1"
  local disk="$(get_disk_from_partition "${partition}")"
  local partnr="$(echo "$partition" | sed "s|$disk||")"
  echo -e "i\n$partnr\nq\n" | gdisk "$disk" | grep -i "partition unique guid" | awk -F\: '{ print $2 }' | awk '{ print $1 }' 2> /dev/null
}

# set_guid partition guid flag
# sets gpt uuid of a partition (works only with efi)
set_guid(){
  local partition="$1"
  local guid="$2"
  [ -z "$partition" -a -z "$guid" ] && return 1
  echo "Restauriere Partitions-GUID von $partition."
  local disk="$(get_disk_from_partition "${partition}")"
  local partnr="$(echo "$partition" | sed "s|$disk||")"
  echo -e "x\nc\n$partnr\n$guid\nw\nY\n" | gdisk "$disk" &> /tmp/image.log || return 1
}

# prepare_fs directory partition
# Removes all files from ${RSYNC_EXCLUDE} and saves win7 boot configuration in
# the root directory of the os.
prepare_fs(){
  (
    # remove excluded files
    cd "$1" || return 1
    local i=""
    for i in ${RSYNC_EXCLUDE}; do # Expand patterns
      if [ -e "$i" ]; then
        echo "Entferne $i."
        rm -rf "$i"
      fi
    done
    # save win7 bcd & mbr
    local targetdir
    # in case of efi save the windows efi files
    local efipart="$(print_efipart)"
    if [ -n "$efipart" ]; then
      # save partition uuids
      echo "Sichere Partitions-GUIDs."
      print_guid "$efipart" > /mnt/.guid.efi
      print_guid "$2" > /mnt/.guid."$(basename "$2")"
      if [ "$(fstype $2)" = "ntfs" ]; then
        targetdir="$(ls -d [Ee][Ff][Ii]/[Mm][Ii][Cc][Rr][Oo][Ss][Oo][Ff][Tt]/[[Bb][Oo][Oo][Tt] 2> /dev/null)"
        [ -z "$targetdir" ] && targetdir="EFI/Microsoft/Boot"
        save_efi_bcd "$targetdir" "$efipart" | tee -a /tmp/linbo_image.log
      fi
    else
      targetdir="$(ls -d [Bb][Oo][Oo][Tt] 2> /dev/null)"
    fi
    if [ -n "$targetdir" ]; then
      local bcd="$(ls $targetdir/[Bb][Cc][Dd] 2> /dev/null)"
      local group="$(hostgroup)"
      if [ -n "$bcd" -a -n "$group" ]; then
        echo "Sichere Windows-BCD fuer Gruppe $group."
        # BCD group specific and partition specific on efi systems
        if [ -n "$efipart" ]; then
          cp -f "$bcd" "$bcd"."$group"."$(basename "$2")"
        else
          cp -f "$bcd" "$bcd"."$group"
        fi
        # boot sector backup group specific
        # delete obsolete mbr backups
        rm -f "$targetdir/winmbr.$group" "$targetdir/win7mbr.$group" "$targetdir/winmbr446.$group" "$targetdir/bsvbr.$group"
        local disk="$(get_disk_from_partition "$2")"
        local bsmbr="$targetdir/bsmbr.$group"
        # save bootloader sectors
        echo "Sichere Bootloadersektoren fuer Gruppe $group."
        dd if="$disk" of="$bsmbr" bs=446 count=1 2>> /tmp/linbo.log
        # in case of gpt partition label
        if fdisk -l "$disk" | grep -q "GPT"; then
          echo "Sichere GPT-Label fuer Gruppe $group."
          local gptlabel="$targetdir/gptlabel.$group"
          dd if="$disk" of="$gptlabel" bs=512 count=34 2>> /tmp/linbo.log
        fi
        # ntfs partition id
        echo "Sichere NTFS-ID."
        local ntfsid="$targetdir/ntfs.id"
        dd if="$2" of="$ntfsid" bs=8 count=1 skip=9 2>> /tmp/linbo.log
      fi
    fi
  )
}

# mk_cloop type inputdev imagename baseimage [timestamp]
mk_cloop(){
  echo "## $(date) : Starte Erstellung von $3." | tee -a /tmp/image.log
  #echo -n "mk_cloop " ;  printargs "$@" | tee -a /tmp/image.log
  # kill torrent process for this image
  local pid="$(ps w | grep ctorrent | grep "$3.torrent" | grep -v grep | awk '{ print $1 }')"
  [ -n "$pid" ] && kill "$pid"
  # remove torrent files
  [ -e "/cache/$3.torrent" ] && rm "/cache/$3.torrent"
  [ -e "/cache/$3.complete" ] && rm "/cache/$3.complete"
  local RC=1
  local size="$(get_partition_size $2)"
  if [ -z "$size" ]; then
    echo "Cannot get size of $2!" 2>&1 | tee -a /tmp/image.log
    return 1
  fi
  local imgsize=0
  case "$1" in
    partition) # full partition dump
      if mountpart "$2" /mnt -w 2>> /tmp/linbo.log; then
        echo "Preparing partition $2 (size=${size}K) for compression..." | tee -a /tmp/image.log
        prepare_fs /mnt "$2" | tee -a /tmp/image.log
        echo "Filling empty space with 0s..." | tee -a /tmp/image.log
        # Create nulled files of size 1GB, should work on any FS.
        local count=0
        while true; do
          # tschmitt: log errors to image.log
          #interruptible dd if=/dev/zero of="/mnt/zero$count.tmp" bs=1024k count=1000 2>/dev/null || break
          interruptible dd if=/dev/zero of="/mnt/zero$count.tmp" bs=1024k count=1000 2>>/tmp/image.log || break
          [ -s "/mnt/zero$count.tmp" ] || break
          let count++
          echo "$(du -ch /mnt/zero*.tmp | tail -1 | awk '{ print $1 }') genullt... " | tee -a /tmp/image.log
        done
        # Sync is asynchronous, unless started twice at least.
        sync ; sync ; sync
        rm -f /mnt/zero*.tmp
        # we don't need the file list anymore
        #echo "Dateiliste erzeugen..." | tee -a /tmp/image.log
        #( cd /mnt/ ; find . | sed 's,^\.,,' ) > "$3".list
        umount /mnt || umount -l /mnt
      fi
      echo "Starting compression of $2 -> $3 (full partition, ${size}K)." | tee -a /tmp/image.log
      echo "create_compressed_fs -B $CLOOP_BLOCKSIZE -L 1 -t 2 -s ${size}K $2 $3" | tee -a /tmp/image.log
      interruptible create_compressed_fs -B "$CLOOP_BLOCKSIZE" -L 1 -t 2 -s "${size}K" "$2" "$3" 2>&1 ; export RC="$?"
      if [ "$RC" = "0" ]; then
        # create status file
        if mountpart "$2" /mnt -w 2>> /tmp/linbo.log; then
          echo "${3%.cloop}" > /mnt/.linbo
          umount /mnt || umount -l /mnt
        fi
        # create info file
        imgsize="$(get_filesize $3)"
        # Adjust uncompressed image size with one additional cloop block
        size="$(($CLOOP_BLOCKSIZE / 1024 + $size))"
        mk_info "$3" "$2" "$size" "$imgsize" >"$3".info
        echo "Done." | tee -a /tmp/image.log
        ls -l "$3"
      else
        echo "Creation of $3 failed. :(" | tee -a /tmp/image.log
        return "$RC"
      fi
      ;;
    differential)
      if mountpart "$2" /mnt -w 2>> /tmp/linbo.log; then
        rmmod cloop >/dev/null 2>&1
        #    echo "modprobe cloop file=/cache/$4" | tee -a /tmp/image.log
        if test -s /cache/"$4" && modprobe cloop file=/cache/"$4"; then
          mkdir -p /cloop
          if mountpart /dev/cloop /cloop -r 2>> /tmp/linbo.log; then
            echo "Starting compression of $2 -> $3 (differential)." | tee -a /tmp/image.log
            prepare_fs /mnt "$2" | tee -a /tmp/image.log
            # determine rsync opts due to fstype
            local type="$(fstype "$2")"
            case $type in
              *ntfs*) ROPTS="-HazAX" ;;
              *vfat*) ROPTS="-rtz" ;;
              *) ROPTS="-az" ;;
            esac
            interruptible rsync "$ROPTS" --exclude="/.linbo" --exclude-from="/etc/rsync.exclude" --delete --delete-excluded --log-file=/tmp/image.log --log-file-format="" --only-write-batch="$3" /mnt/ /cloop 2>&1 ; RC="$?"
            umount /cloop
            if [ "$RC" = "0" ]; then
              imgsize="$(get_filesize $3)"
              mk_info "$3" "$2" "" "$imgsize" >"$3".info
              echo "Done." | tee -a /tmp/image.log
              ls -l "$3"
            else
              echo "Creation of $3 failed. :(" | tee -a /tmp/image.log
              return "$RC"
            fi
          else
            RC="$?"
            # DEBUG, REMOVEME
            dmesg | tail -5 >&2
          fi
        else
          echo "Image $4 cannot be mounted, which is essential for differential imaging." | tee -a /tmp/image.log
          RC="$?"
        fi
        rmmod cloop >/dev/null 2>&1
        umount /mnt || umount -l /mnt
      else
        RC="$?"
      fi
      ;;
  esac
  # create torrent file
  if [ "$RC" = "0" ]; then
    echo "Creating torrent files ..." | tee -a /tmp/image.log
    touch "$3".complete
    local serverip="$(grep -i ^server /start.conf | awk -F\= '{ print $2 }' | awk '{ print $1 }')"
    ctorrent -t -u http://"$serverip":6969/announce -s "$3".torrent "$3" | tee -a /tmp/image.log
  fi
  echo "## $(date) : Creation of $3 finished." | tee -a /tmp/image.log
  return "$RC"
}

# check_status partition imagefile:
# returns true if mountable & contains a version of the archive.
check_status(){
  local RC=1
  local base="${2##*/}"
  base="${base%.[Cc][Ll][Oo]*}"
  base="${base%.[Rr][Ss][Yy]*}"
  mountpart "$1" /mnt -r 2>> /tmp/linbo.log || return $?
  [ -s /mnt/.linbo ] && case "$(cat /mnt/.linbo 2>/dev/null)" in *$base*) RC=0 ;; esac
  umount /mnt || umount -l /mnt
  # [ "$RC" = "0" ] && echo "Enthaelt schon eine Version von $2."
  return "$RC"
}

# update_status partition imagefile:
# Add information about installed archives to partition
update_status(){
  local base="${2##*/}"
  base="${base%.[Cc][Ll][Oo]*}"
  base="${base%.[Rr][Ss][Yy]*}"
  mountpart "$1" /mnt -w 2>> /tmp/linbo.log || return $?
  case "$2" in *.[Cc][Ll][Oo]*) rm -f /mnt/.linbo ;; esac
  echo "$base" >> /mnt/.linbo
  sync; sync; sleep 1
  umount /mnt || umount -l /mnt
  return 0
}

# restore complete ntfs cloop image, which is assigned to /dev/cloop
# cp_cloop_ntfs targetdev
cp_cloop_ntfs(){
  local RC=1
  local targetdev="$1"
  echo "Restauriere Partition $targetdev mit ntfsclone..." | tee -a /tmp/image.log
  interruptible ntfsclone -q -f --overwrite "$targetdev" /dev/cloop 2>&1 | tee -a /tmp/image.log
  # in case of cluster referenced twice error give sync a second chance
  if (grep -i error /tmp/image.log | grep -i cluster | grep -qi 'referenced twice' && [ ! -e /tmp/.twice.done ]); then
    touch /tmp/.twice
    RC="0"
  elif grep -qi error /tmp/image.log; then
    echo 'FEHLER!' | tee -a /tmp/image.log
    return "$RC"
  fi
  # check if resizing is necessary
  echo "Pruefe ob Dateisystem vergroessert werden muss..." | tee -a /tmp/image.log
  # save ntfs size infos in temp file
  ntfsresize -f -i "$targetdev" 2>> /tmp/image.log > /tmp/ntfs.info
  # get volume size in mb
  local volsizemb="$(grep "Current volume size" /tmp/ntfs.info | awk -F\( '{ print $2 }' | awk '{ print $1}')"
  # test if volsizemb is an integer value
  if ! isinteger "$volsizemb"; then
    echo "Kann Dateisystemgroesse nicht bestimmen." | tee -a /tmp/image.log
    return 1
  fi
  echo "Dateisystem: $volsizemb MB" | tee -a /tmp/image.log
  # get partition size in mb
  local devsizemb="$(grep "Current device size" /tmp/ntfs.info | awk -F\( '{ print $2 }' | awk '{ print $1}')"
  # test if devsizemb is an integer value
  if ! isinteger "$devsizemb"; then
    echo "Kann Partitionsgroesse nicht bestimmen." | tee -a /tmp/image.log
    return 1
  fi
  echo "Partition  : $devsizemb MB" | tee -a /tmp/image.log
  # test if partition is larger than filesystem and adjust filesystem size if necessary
  if [ $devsizemb -gt $volsizemb ]; then
    echo "Dateisystem wird auf $devsizemb MB vergroessert." | tee -a /tmp/image.log
    # get partition size in bytes
    local devsize="$(grep "Current device size" /tmp/ntfs.info | awk '{ print $4}')"
    if ! isinteger "$devsize"; then
      echo "Kann Partitionsgroesse nicht bestimmen." | tee -a /tmp/image.log
      return 1
    fi
    # increase the filesystem size
    echo -e "y\n" | ntfsresize -f -s "$devsize" "$targetdev" ; RC="$?"
    [ "$RC" = "0" ] || echo "Vergroesserung von $targetdev ist fehlgeschlagen." | tee -a /tmp/image.log
  else
    echo "Vergroesserung ist nicht notwendig." | tee -a /tmp/image.log
    RC=0
  fi # devsizemb gt volsizemb
  return "$RC"
} # cp_cloop_ntfs

# INITIAL copy
# cp_cloop imagefile targetdev
cp_cloop(){
  echo "## $(date) : Starte Komplettrestore von $1." | tee -a /tmp/image.log
  echo -n "cp_cloop " ;  printargs "$@" | tee -a /tmp/image.log
  local RC=1
  local imagefile="$1"
  local targetdev="$2"
  rmmod cloop >/dev/null 2>&1
  # repair cloop link if vanished
  [ -e /dev/cloop ] || ln -s /dev/cloop0 /dev/cloop
  if test -s "$imagefile" && modprobe cloop file=/cache/"$imagefile"; then
    # restore ntfs partitions with ntfsclone
    if [ "$(fstype $targetdev)" = "ntfs" ]; then
      cp_cloop_ntfs "$targetdev" ; RC="$?"
    else
      # old cp_cloop stuff
      local s1="$(get_partition_size /dev/cloop0)"
      local s2="$(get_partition_size $targetdev)"
      local block="$(($CLOOP_BLOCKSIZE / 1024))"
      if [ "$(($s1 - $block))" -gt "$s2" ] 2>/dev/null; then
        echo "FEHLER: CLOOP-Image $imagefile (${s1}K) ist groesser als Partition $targetdev (${s2}K)" >&2 | tee -a /tmp/image.log
        echo 'FEHLER: Das passt nicht!' >&2 | tee -a /tmp/image.log
        rmmod cloop >/dev/null 2>&1
        return 1
      fi
      # Userspace program MAY be faster than kernel module (no kernel lock necessary)
      # Forking an additional dd makes use of a second CPU and speeds up writing
      ( interruptible extract_compressed_fs /cache/"$imagefile" - | dd of="$targetdev" bs=1M ) 2>> /tmp/linbo.log | tee -a /tmp/image.log
      #interruptible extract_compressed_fs /cache/"$1" "$2" 2>&1 | tee -a /tmp/image.log
      # interruptible dd if=/dev/cloop of="$2" bs=1024k
      RC="$?"
    fi
  else
    # cloop file could not be mounted
    RC="$?"
    # DEBUG, REMOVEME
    dmesg | tail -5
    echo "Fehler: Archiv \"$imagefile\" nicht vorhanden bzw. defekt oder Zielpartition \"$targetdev\" zu klein bzw. fehlerhaft." >&2 | tee -a /tmp/image.log
  fi
  rmmod cloop >/dev/null 2>&1
  [ "$RC" = "0" ] && update_status "$targetdev" "$imagefile"
  echo "## $(date) : Beende Komplettrestore von $imagefile." | tee -a /tmp/image.log
  return "$RC"
}


# differential/Synced
# sync_cloop imagefile targetdev
sync_cloop(){
  echo "## $(date) : Starte Synchronisation von $1." | tee -a /tmp/image.log
  # echo -n "sync_cloop " ;  printargs "$@"
  local RC=1
  local ROPTS="-HaAX"
  #local TMP=/tmp/sync_tmp.log
  #local ROPTS="-a"
  [ "$(fstype "$2")" = "vfat" ] && ROPTS="-rt"
  if mountpart "$2" /mnt -w 2>> /tmp/linbo.log; then
    case "$1" in
      *.[Rr][Ss][Yy]*)
        # tschmitt: added logging parameter
        #interruptible rsync "$ROPTS" --fake-super --compress --partial --delete --log-file=/tmp/image.log --log-file-format="" --read-batch="$1" /mnt >"$TMP" 2>&1 ; RC="$?"
        echo "Synchronisation laeuft ... bitte warten ..."
        #interruptible rsync "$ROPTS" --compress --delete --log-file=/tmp/image.log --log-file-format="" --read-batch="$1" /mnt >"$TMP" 2>&1 ; RC="$?"
        interruptible rsync "$ROPTS" --compress --delete --log-file=/tmp/image.log --log-file-format="" --read-batch="$1" /mnt 2>&1 ; RC="$?"
        if [ "$RC" != "0" ]; then
          cat "$TMP" >&2 | tee -a /tmp/image.log
          echo "Fehler beim Synchronisieren des differentiellen Images \"$1\" nach $2, rsync-Fehlercode: $RC." >&2 | tee -a /tmp/image.log
          #sleep 5
        fi
        rm -f "$TMP"
        ;;
      *.[Cc][Ll][Oo]*)
        rmmod cloop >/dev/null 2>&1
        echo "modprobe cloop file=/cache/$1"  | tee -a /tmp/image.log
        if test -s "$1" && modprobe cloop file=/cache/"$1"; then
          mkdir -p /cloop
          if mountpart /dev/cloop /cloop -r 2>> /tmp/linbo.log; then
            echo "Synchronisation laeuft ... bitte warten ..."
            interruptible rsync "$ROPTS" --exclude="/.linbo" --exclude-from="/etc/rsync.exclude" --delete --delete-excluded --log-file=/tmp/image.log --log-file-format="" /cloop/ /mnt 2>&1 ; RC="$?"
            umount /cloop
            if [ "$RC" != "0" ]; then
              cat "$TMP" >&2 | tee -a /tmp/image.log
              echo "Fehler beim Restaurieren des Images \"$1\" nach $2, rsync-Fehlercode: $RC." >&2 | tee -a /tmp/image.log
              #sleep 5
            fi
            rm -f "$TMP"
          else
            RC="$?"
            # DEBUG/REMOVEME
            dmesg | tail -5
            echo "Fehler: /cloop kann nicht vom Image \"$1\" gemountet werden." >&2 | tee -a /tmp/image.log
          fi
        else
          RC="$?"
          echo "Fehler: Image \"$1\" fehlt oder ist defekt." >&2 | tee -a /tmp/image.log
        fi
        rmmod cloop >/dev/null 2>&1
        ;;
    esac
    sync; sync; sleep 1
    umount /mnt || umount -l /mnt
  else
    RC="$?"
  fi
  [ "$RC" = "0" ] && update_status "$2" "$1"
  echo "## $(date) : Beende Synchronisation von $1." | tee -a /tmp/image.log
  return "$RC"
}

# restore imagefile targetdev [force]
restore(){
  #echo -n "restore " ;  printargs "$@"
  local RC=1
  local disk="${2%%[1-9]*}"
  local force="$3"
  local fstype="$(fstype_startconf "$2")"
  local label="$(print_partlabel "$2")"
  echo -n "Entpacke: $1 -> $2 "
  case "$1" in
    *.[Cc][Ll][Oo]*)
      if [ "$force" != "force" ]; then
        check_status "$2" "$1" || force="force"
      fi
      if [ "$force" = "force" ]; then
        echo "[Komplette Partition]..."
        format "$2" "$fstype" "$label" 2>> /tmp/linbo.log || return 1
      else
        echo "[Datei-Sync]..."
      fi
      if [ "$force" = "force" -a "$fstype" = "ntfs" ]; then
        cp_cloop "$1" "$2" ; RC="$?"
        if [ -e /tmp/.twice -a ! -e /tmp/.twice.done ]; then
          echo "Referenced-Twice-Fehler bei ntfsclone, versuche Datei-Sync..."
          sync_cloop "$1" "$2" ; RC="$?"
          touch /tmp/.twice.done
        fi
      else
        sync_cloop "$1" "$2" ; RC="$?"
      fi
      ;;
    *.[Rr][Ss][Yy]*)
      echo "[Datei-Sync]..."
      sync_cloop "$1" "$2" ; RC="$?"
      ;;
  esac
  if [ "$RC" = "0" ]; then
    # ntfsfix after sync
    #[ "$fstype" = "ntfs" ] && ntfsfix -d "$2"
    echo "Fertig."
  else
    echo "Fehler!" >&2
    return "$RC"
  fi
  return "$RC"
}

# tschmitt
# patch fstab with root partition and root fstype: patch_fstab rootdev
patch_fstab(){
  echo -n "patch_fstab " ;  printargs "$@"
  local rootdev="$1"
  local line=""
  local found=""
  local fstype_mount=""
  local fstype_fstab=""
  local mntpnt=""
  local changed=""
  local rootdev_fstab=""
  local options=""
  local dump=""
  local pass=""
  [ -z "$rootdev" ] && return 1
  [ -e "$rootdev" ] || return 1
  [ -e /tmp/fstab ] && rm -f /tmp/fstab
  while read line; do
    if [ -n "$line" -a "${line:0:1}" != "#" ]; then
      mntpnt="$(echo "$line" | awk '{ print $2 }')"
      if [ "$mntpnt" = "/" -a -z "$found" ]; then
        found=yes
        rootdev_fstab="$(echo "$line" | awk '{ print $1 }')"
        [ -z "$rootdev_fstab" ] && return 1
        fstype_fstab="$(echo "$line" | awk '{ print $3 }')"
        [ -z "$fstype_fstab" ] && return 1
        options="$(echo "$line" | awk '{ print $4 }')"
        [ -z "$options" ] && return 1
        dump="$(echo "$line" | awk '{ print $5 }')"
        [ -z "$dump" ] && return 1
        pass="$(echo "$line" | awk '{ print $6 }')"
        [ -z "$pass" ] && return 1
        if [ "$rootdev_fstab" != "$rootdev" ]; then
          # change root partition if necessary
          echo "Setze Rootpartition: $rootdev."
          rootdev_fstab="$rootdev"
          line="$rootdev_fstab $mntpnt $fstype_fstab $options $dump $pass"
          changed=yes
        fi # rootdev
        # check for changed filesytem type if partition was formatted
        fstype_mount="$(cat /proc/mounts | grep "^$rootdev" | awk '{ print $3 }')"
        [ -z "$fstype_mount" ] && return 1
        if [ "$fstype_fstab" != "$fstype_mount" ]; then
          # change filesystem
          echo "Setze Dateisystem: $fstype_mount."
          fstype_fstab="$fstype_mount"
          line="$rootdev_fstab $mntpnt $fstype_fstab $options $dump $pass"
          changed=yes
        fi # fstype
      fi # mntpnt
    fi # line
    echo "$line" >> /tmp/fstab
  done </mnt/etc/fstab # reading fstab
  if [ -n "$changed" ]; then
    mv -f /mnt/etc/fstab /mnt/etc/fstab.bak
    mv -f /tmp/fstab /mnt/etc
  fi
}

# process opsi stuff if client is installed: do_opsi baseimage image
do_opsi(){
  # search for opsi's client config on windows partition
  local opsidir="$(ls -d /mnt/[Pp][Rr][Oo][Gg][Rr][Aa][Mm]*/opsi.org/opsi-client-agent/opsiclientd 2> /dev/null)"
  [ -d "$opsidir" ] || return 0
  local opsiconf="$opsidir/opsiclientd.conf"
  [ -s "$opsiconf" ] || return 0
  echo "Starte OPSI-Client-Konfiguration:"
  local ip="$(grep ^ip /tmp/dhcp.log | awk -F\' '{ print $2 }')"
  local domainname="$(grep ^domain /tmp/dhcp.log | awk -F\' '{ print $2 }' | tail -1)"
  local fqdn="$(hostname).$domainname"
  local depotid="opsi.$domainname"
  local serverip="$(grep ^linbo_server /tmp/dhcp.log | tail -1 | awk -F\' '{ print $2 }')"
  local opsiip
  local opsiserverpem
  local opsiclientpem="$opsidir/opsiclientd.pem"
  local image
  local clientkey
  local RC="0"
  if [ -n "$2" ]; then
    image="$2"
  else
    image="$1"
  fi
  # request opsikey
  localmode || rsync "$serverip"::linbo/"$ip.opsikey" /tmp/opsikey
  if [ -s /tmp/opsikey ]; then
    echo " * Host-Key heruntergeladen."
    clientkey="$(cat /tmp/opsikey)"
    # save opsi host key for offline use
    remote_cache "$(cachedev)" || cp /tmp/opsikey /cache
  else
    # load opsi from cache
    [ -s /cache/opsikey ] && key="$(cat /cache/opsikey)"
  fi
  if [ -n "$clientkey" ]; then
    # patch opsi host key & id
    echo " * Host-Key gepatcht."
    sed -e "s|^host_id.*|host_id = $fqdn|" -e "s|^opsi_host_key.*|opsi_host_key = $clientkey|" -i "$opsiconf" || RC="1"
    # patch depot id
    echo " * Depot-ID $depotid gepatcht."
    sed -i "s|^depot_id.*|depot_id = $depotid|" "$opsiconf" || RC="1"
    # get opsi ip
    if [ -s /tmp/opsiip ]; then
      opsiip="$(cat /tmp/opsiip)"
      echo " * IP $opsiip vom Server erhalten."
      # save opsi ip for offline use
      remote_cache "$(cachedev)" || cp /tmp/opsiip /cache
    elif [ -s /cache/opsiip ]; then
      # load opsi ip from cache
      opsiip="$(cat /cache/opsiip)"
      echo " * IP $opsiip aus dem Cache geholt."
    fi
    if [ -n "$opsiip" ]; then
      echo " * IP $opsiip gepatcht."
      sed -i "s|^url = https.*|url = https://$opsiip:4447/rpc|" "$opsiconf" || RC="1"
    fi
    # get opsi server cert
    if [ -s "/tmp/opsiconfd.pem" ]; then
      echo " * Zertifikat heruntergeladen."
      opsiserverpem="/tmp/opsiconfd.pem"
      remote_cache "$(cachedev)" || cp "$opsiserverpem" /cache
    elif [ -s "/cache/opsiconfd.pem" ]; then
      echo " * Zertifikat aus dem Cache geholt."
      opsiserverpem="/cache/opsiconfd.pem"
    fi
    # provide opsi server cert
    if [ -n "$opsiserverpem" ]; then
      echo " * Zertifikat kopiert."
      cp "$opsiserverpem" "$opsiclientpem" || RC="1"
    fi
    # evaluate return code
    if [ "$RC" = "0" ]; then
      echo " * Erfolgreich beendet."
    else
      echo " * Aktualisierung war nicht moeglich!"
    fi
  else
    echo " * Host-Key nicht verfuegbar!"
  fi
  rm -f /tmp/opsikey
  # request opsi host ini update
  localmode || rsync "$serverip"::linbo/"$image.opsi" /cache &> /dev/null || true
  return "$RC"
}

# restore windows activation tokens
restore_winact(){
  # get image name
  [ -s  /mnt/.linbo ] && local image="$(cat /mnt/.linbo)"
  # if an image is not yet created do nothing
  if [ -z "$image" ]; then
    echo "Ueberspringe Reaktivierung, System ist unsynchronisiert."
    return
  fi
  local archive
  local tarchive
  local i
  # get mac address
  local mac="$(mac | tr a-z A-Z)"
  # without linbo server
  if localmode || [ -z "$mac" ] || [ "$mac" = "OFFLINE" ]; then
    tarchive="$(cd /cache && ls *.$image.winact.tar.gz 2> /dev/null)"
    # get mac address from archive name
    for i in $tarchive; do
      mac="$(echo $i | awk -F\. '{ print $1 }')"
      if ifconfig -a | grep -q "$mac"; then
        archive="$i"
        break
      fi
    done
  else # with linbo server
    archive="$mac.$image.winact.tar.gz"
    # get server ip address
    local serverip="$(grep ^linbo_server /tmp/dhcp.log | tail -1 | awk -F\' '{ print $2 }')"
    echo -n "Fordere Reaktivierungs-Daten von $serverip an ... "
    # get token archive from linbo server
    rsync "$serverip"::linbo/winact/"$archive" /cache &> /dev/null
    if [ -s "/cache/$archive" ]; then
      echo "OK!"
    else
      echo "ueberspringe Reaktivierung, keine Daten!"
      return
    fi
    # request windows/office productkeys
    local keyfile="$(ifconfig -a | md5sum | awk '{ print $1 }').winkey"
    rsync "$serverip"::linbo/winact/"$keyfile" /cache &> /dev/null
    [ -s "/cache/$keyfile" ] && source "/cache/$keyfile"
    # create windows key batchfile
    local cache_batchfile="/cache/$image.winact.cmd"
    # remove old batchfile
    rm -f "$cache_batchfile"
    # create new one if keys are provided
    if [ -n "$winkey" ]; then
      echo "cscript.exe %SystemRoot%\\System32\\slmgr.vbs -ipk $winkey" > "$cache_batchfile"
    fi
    # add office key handling to batchfile if office token is in archive
    if gunzip -c "/cache/$archive" | tar -t | grep -qi office 2> /dev/null; then
      if [ -n "$officekey" ]; then
        # get path to ospp.vbs
        local ospp="$(ls /mnt/[Pp][Rr][Oo][Gg][Rr][Aa][Mm]\ [Ff][Ii][Ll][Ee][Ss]*/[Mm][Ii][Cc][Rr][Oo][Ss][Oo][Ff][Tt]\ [Oo][Ff][Ff][Ii][Cc][Ee]/[Oo][Ff][Ff][Ii][Cc][Ee]*/[Oo][Ss][Pp][Pp].[Vv][Bb][Ss] 2> /dev/null)"
        if [ -n "$ospp" ]; then
          # compute windows path to office installation dir
          local ospp_win_path="$(echo "$ospp" | sed 's|/mnt/|%SystemDrive%\\|' | sed 's|/|\\|g' )"
          # write office activations commands to batchfile
          echo "cscript.exe \"$ospp_win_path\" /inpkey:$officekey" >> "$cache_batchfile"
          echo "cscript.exe \"$ospp_win_path\" /act" >> "$cache_batchfile"
        fi
      fi
    fi
    rm -f "$keyfile"
  fi # localmode
  # copy reactivation batchfile to windows partition
  if [ -s "$cache_batchfile" ]; then
    local win_batchfile="/mnt/linuxmuster-win/winact.cmd"
    if [ -n "$winkey" -a -n "$officekey" ]; then
      local keymsg="Windows- und Office-Keys"
    elif [ -n "$winkey" ]; then
      local keymsg="Windows-Key"
    else
      local keymsg="Office-Key"
    fi
    echo "Erstelle Reaktivierungskript mit $keymsg in $win_batchfile."
    dos2unix "$cache_batchfile"
    cp "$cache_batchfile" "$win_batchfile"
  else # no keys, no batchfile
    echo "Keine Produktkeys verfuegbar, erstelle kein Reaktivierungskript."
  fi
  if [ -n "$archive" -a -s "/cache/$archive" ]; then
    echo "Stelle Windows-Aktivierungstokens wieder her."
    if ! tar xf "/cache/$archive" -C /; then
      echo "Fehler beim Entpacken von $archive!"
      return 1
    fi
  fi
}


# print windows version
winver(){
  local hive="$(ls -1d /mnt/[Ww][Ii][Nn][Dd][Oo][Ww][Ss]/[Ss][Yy][Ss][Tt][Ee][Mm]32/[Cc][Oo][Nn][Ff][Ii][Gg]/[Ss][Oo][Ff][Tt][Ww][Aa][Rr][Ee] 2> /dev/null)"
  [ -z "$hive" ] && return 1
  local winverhex="$(echo -e "cd \Microsoft\Windows NT\CurrentVersion\ncat CurrentMajorVersionNumber\nq\n" | reged -e "$hive" | grep ^0x 2> /dev/null)"
  [ -z "$winverhex" ] && return 1
  printf "%d\n" "$winverhex"
}


# syncl cachedev baseimage image bootdev rootdev kernel initrd append [force]
syncl(){
  local RC="1"
  local patchfile=""
  local postsync=""
  local rootdev="$5"
  local disk="$(get_disk_from_partition "$rootdev")"
  local group="$(hostgroup)"
  local bootdir

  # don't sync in that case
  if [ "$1" = "$rootdev" ]; then
    echo "Skipping local synchronisation. Image $2 will be started direct from cache."
    return 0
  fi

  # begin syncing
  echo -n "syncl " ; printargs "$@"

  # mount cache and sync
  mountcache "$1" || return "$?"
  cd /cache
  local image=""
  local i=""
  # start syncing images
  for i in "$2" "$3"; do
    [ -n "$i" -a -f "$i" ] || continue
    image="$i"
    restore "$image" "$5" $9 ; RC="$?"
    [ "$RC" = "0" ] || break
    patchfile="$image.reg"
    postsync="$image.postsync"
  done

  # mount os partition
  if [ "$RC" = "0" ]; then
    mountpart "$5" /mnt -w 2>> /tmp/linbo.log ; RC="$?"
  fi
  # return on error
  if [ "$RC" != "0" ]; then
    echo "Cannot mount $5!" | tee -a /tmp/linbo.log
    sendlog
    cd /
    return "$RC"
  fi

  # detect windows os
  [ -e /mnt/[Nn][Tt][Ll][Dd][Rr] -o -e /mnt/[Bb][Oo][Oo][Tt][Mm][Gg][Rr] -o -d /mnt/[Ww][Ii][Nn][Dd][Oo][Ww][Ss]/[Ss][Yy][Ss][Tt][Ee][Mm]32 ] && local is_win="yes"

  # get hostname
  local HOSTNAME
  local FQDN
  if [ -s /tmp/network.ok ]; then
    source /tmp/network.ok
    FQDN="${hostname}.${domain}"
    HOSTNAME="$hostname"
  elif [ -s /cache/hostname ]; then
    FQDN="$(cat /cache/hostname)"
    HOSTNAME="$(echo "$FQDN" | awk -F \. '{ print $1 }')"
    [ "$FQDN" = "$HOSTNAME" ] && FQDN=""
  fi
  [ -z "$HOSTNAME" ] && HOSTNAME="$(hostname)"

  # detect efi system if efi partition exists
  local efipart="$(print_efipart)"

  ## Prepare os filesystem, apply patches etc.

  # restore guids in case of efi/gpt partitions
  local sysname
  if [ -n "$efipart" ]; then
    sysname="EFI"
    # restore partition guids
    local partname="$(basename "$rootdev")"
    # get guids with old method
    [ -s /mnt/.guids ] && source /mnt/.guids
    if [ -s /mnt/.guid.efi ]; then
      set_guid "$efipart" "$(cat /mnt/.guid.efi)"
    else
      [ -n "$guid_efi" ]&& set_guid "$efipart" "$guid_efi"
    fi
    if [ -s "/mnt/.guid.$partname" ]; then
      set_guid "$rootdev" "$(cat /mnt/.guid."$partname")"
    else
      [ -n "$guid_os" ] && set_guid "$rootdev" "$guid_os"
    fi
  else
    sysname="BIOS"
  fi # efipart
  echo "$sysname system found."

  # windows stuff begin
  if [ -n "$is_win" ]; then

    # do registry patching for windows systems
    if [ -s "$patchfile" ]; then
      echo "Patching windows registry with $patchfile." | tee /tmp/patch.log
      sed 's|{\$HostName\$}|'"$HOSTNAME"'|g' "$patchfile" > "$TMP"
      dos2unix "$TMP"
      cat "$TMP" >>/tmp/patch.log
      patch_registry "$TMP" /mnt 2>&1 >>/tmp/patch.log
      [ -e /tmp/output ] && cat /tmp/output >>/tmp/patch.log
      rm -f "$TMP"
    fi # reg patch

    # tweak windows newdev.dll (suppresses new hardware dialog)
    local newdevdll="$(ls /mnt/[Ww][Ii][Nn][Dd][Oo][Ww][Ss]/[Ss][Yy][Ss][Tt][Ee][Mm]32/[Nn][Ee][Ww][Dd][Ee][Vv].[Dd][Ll][Ll] 2> /dev/null)"
    [ -z "$newdevdll" ] && newdevdll="$(ls /mnt/[Ww][Ii][Nn][NN][Tt]/[Ss][Yy][Ss][Tt][Ee][Mm]32/[Nn][Ee][Ww][Dd][Ee][Vv].[Dd][Ll][Ll] 2> /dev/null)"
    local newdevdllbak="$newdevdll.linbo-orig"
    # save original file
    [ -n "$newdevdll" -a ! -e "$newdevdllbak" ] && cp "$newdevdll" "$newdevdllbak"
    # patch newdev.dll
    if [ -n "$newdevdll" ]; then
      echo -n "Patching $newdevdll ... "
      local bcmd
      local RC_BVI
      # read substitute commands line for line from file
      grep ^s/ /etc/newdev-patch.bvi | while read bcmd; do
        echo "$bcmd" >> /tmp/patch.log
        bvi -c "$bcmd" +"w" +"q" "$newdevdll" 2>&1 >> /tmp/patch.log || RC_BVI="1"
        [ "$RC_BVI" = "1" ] && break
      done
      if [ "$RC_BVI" = "1" ]; then
        echo "Error! See patch.log."
        RC="$RC_BVI"
      else
        echo "Ok!"
      fi
    fi

    # restore windows boot files
    local bcd_backup

    # get boot files
    # efi
    if [ -n "$efipart" ]; then
      # detect efi boot dir backup
      bootdir="$(ls -d /mnt/[Ee][Ff][Ii]/[Mm][Ii][Cc][Rr][Oo][Ss][Oo][ff][Tt]/[Bb][Oo][Oo][Tt] 2> /dev/null)"
      # create efi boot dir from bios boot stuff if not exists
      if [ -z "$bootdir" ]; then
        bootdir="/mnt/EFI/Microsoft/Boot"
        mkdir -p "$bootdir"
        local oldbootdir="$(ls -d /mnt/[Bb][Oo][Oo][Tt] 2> /dev/null)"
        if [ -n "$oldbootdir" ]; then
          cp -r "$oldbootdir"/* "$bootdir/"
        fi
        local srcdir="$(ls -d /mnt/[Ww][Ii][Nn][Dd][Oo][Ww][Ss]/[Bb][Oo][Oo][Tt]/[Ee][Ff][Ii] 2> /dev/null)"
        [ -n "$srcdir" ] && cp -r "$srcdir"/* "$bootdir/"
      fi
      bcd_backup="$bootdir"/BCD."$group"."$partname"
      # bios
    else
      bootdir="$(ls -d /mnt/[Bb][Oo][Oo][Tt])"
      bcd_backup="$bootdir"/BCD."$group"
    fi

    # restore bcd
    if [ -s "$bcd_backup" ]; then
      echo "Restoring windows bcd from $(basename "$bcd_backup")."
      cp -f "$bcd_backup" "$bootdir"/BCD
    fi

    # restore disk boot sector
    # detect old versions
    local bsmbr="$bootdir"/bsmbr."$group"
    local bsmbr_old="$bootdir"/winmbr446."$group"
    [ -e "$bsmbr_old" ] && mv "$bsmbr_old" "$bsmbr"
    [ -e "$bsmbr" ] || bsmbr="$bootdir"/winmbr."$group"
    [ -e "$bsmbr" ] || bsmbr="$bootdir"/win7mbr."$group"
    if [ -e "$bsmbr" ]; then
      echo "Restoring windows bootloader from $(basename "$bsmbr")" | tee -a /tmp/linbo.log
      case "$bsmbr" in
        *bsmbr.*)
          dd if="$bsmbr" of="$disk" bs=446 count=1 2>> /tmp/linbo.log
          ;;
        *winmbr.*)
          dd if="$bsmbr" of="$disk" bs=1 count=4 seek=440 2>> /tmp/linbo.log
          ;;
        *win7mbr.*)
          dd if="$bsmbr" of="$disk" bs=1 count=4 skip=440 2>> /tmp/linbo.log
          ;;
      esac
    fi
    # restore gpt label
    if fdisk -l "$disk" | grep -q "GPT"; then
      local gptlabel="$bootdir"/gptlabel."$group"
      if [ -s "$gptlabel" ]; then
        echo "Restoring gpt label from $(basename "$gptlabel")." | tee -a /tmp/linbo.log
        dd if="$gptlabel" of="$disk" bs=512 count=34 2>> /tmp/linbo.log
      fi
    fi

    # restore ntfs id
    [ -e "$bootdir"/ntfs.id ] && local ntfsid="$(ls "$bootdir"/ntfs.id 2> /dev/null)"
    if [ -n "$ntfsid" -a -s "$ntfsid" ]; then
      echo "Restoring ntfs-id $(basename "$ntfsid")."
      dd if="$ntfsid" of="$rootdev" bs=8 count=1 seek=9 2>> /tmp/linbo.log
    fi

    # write partition boot sector (vfat and 32bit only)
    if [ "$(fstype "$rootdev")" = "vfat" -a -z "$(get_64)" ]; then
      local msopt=""
      [ -e /mnt/[Nn][Tt][Ll][Dd][Rr] ] && msopt="-2"
      [ -e /mnt/[Ii][Oo].[Ss][Yy][Ss] ] && msopt="-3"
      if [ -n "$msopt" ]; then
        echo "Writing partition boot sector." | tee -a /tmp/patch.log
        ms-sys "$msopt" "$rootdev" | tee -a /tmp/patch.log
      fi
    fi

    # update linuxmuster-win scripts and restore windows activation
    if [ -d /cache/linuxmuster-win ]; then
      update_win "$rootdev" || RC="1"
      if [ "$RC" = "0" ]; then
        restore_winact || RC="1"
      fi
    fi

  fi # windows stuff end

  # linux stuff begin

  # grub efi
  if [ -n "$efipart" -a -d /mnt/boot/grub ]; then
    mkdir -p /mnt/boot/efi
    mount "$efipart" /mnt/boot/efi
    local i
    for i in /dev /dev/pts /proc /sys; do
      mount --bind "$i" /mnt"$i"
    done
    chroot /mnt update-grub
    for i in /sys /proc /dev/pts /dev; do
      umount /mnt"$i"
    done
    umount /mnt/boot/efi
  fi

  # hostname
  if [ -f /mnt/etc/hostname ]; then
    if [ -n "$HOSTNAME" ]; then
      echo "Setting hostname -> $HOSTNAME."
      echo "$HOSTNAME" > /mnt/etc/hostname
    fi
  fi

  # copy ssh keys
  if [ -d /mnt/etc/dropbear ]; then
    cp /etc/dropbear/* /mnt/etc/dropbear
    if [ -s /mnt/root/.ssh/authorized_keys ]; then
      local sshkey="$(cat /.ssh/authorized_keys)"
      grep -q "$sshkey" /mnt/root/.ssh/authorized_keys || cat /.ssh/authorized_keys >> /mnt/root/.ssh/authorized_keys
    else
      mkdir -p /mnt/root/.ssh
      cp /.ssh/authorized_keys /mnt/root/.ssh
    fi
    chmod 600 /mnt/root/.ssh/authorized_keys
  fi

  # patch dropbear config with port 2222 and disable password logins
  if [ -s /mnt/etc/default/dropbear ]; then
    sed -e 's|^NO_START=.*|NO_START=0|
            s|^DROPBEAR_EXTRA_ARGS=.*|DROPBEAR_EXTRA_ARGS=\"-s -g\"|
    s|^DROPBEAR_PORT=.*|DROPBEAR_PORT=2222|' -i /mnt/etc/default/dropbear
  fi

  # fstab
  [ -f /mnt/etc/fstab ] && patch_fstab "$rootdev"

  # linux stuff end

  # opsi stuff
  do_opsi "$2" "$3" || RC="1"

  # source postsync script
  [ -s "/cache/$postsync" ] && . "/cache/$postsync"

  # finally do minimal boot configuration
  mk_boot || RC=1

  # all done
  sync; sync; sleep 1
  umount /mnt || umount -l /mnt

  # restore partition labels
  label_allpartitions

  # log hosts image status
  if [ "$RC" = "0" ]; then
    local timestamp="$(getinfo /cache/$image.info timestamp)"
    log_image_status "$image" "$timestamp" applied
  fi

  sendlog
  cd / # ; mountcache "$1" -r
  return "$RC"
}

# create cachedev imagefile baseimagefile bootdev rootdev kernel initrd
create(){
  echo -n "create " ;  printargs "$@"
  [ -n "$2" -a -n "$1" -a -n "$5" ] || return 1
  mountcache "$1" || return "$?"
  if ! cache_writable; then
    echo "Cache-Partition ist nicht schreibbar, Abbruch." >&2 | tee -a /tmp/image.log
    sendlog
    mountcache "$1" -r
    return 1
  fi
  cd /cache
  local RC="1"
  local type="$(fstype "$5")"
  # ntfsfix before image creation
  #[ "$type" = "ntfs" ] && ntfsfix -d "$5"
  echo "Erzeuge Image '$2' von Partition '$5'..." | tee -a /tmp/image.log
  case "$2" in
    *.[Cc][Ll][Oo]*)
      mk_cloop partition "$5" "$2" "$3" ; RC="$?"
      ;;
    *.[Rr][Ss][Yy]*)
      mk_cloop differential "$5" "$2" "$3" ; RC="$?"
      ;;
  esac
  [ "$RC" = "0" ] && echo "Fertig." || echo "Fehler." >&2
  sendlog
  cd / ; mountcache "$1" -r
  return "$RC"
}

# getinfo file key
getinfo(){
  [ -f "$1" ] || return 1
  while read line; do
    local key="${line%%=*}"
    if [ "$key" = "$2" ]; then
      echo "${line#*=}"
      return 0
    fi
  done <"$1"
  return 1
}

# write hosts image status to logfile
# usage (flag can be "applied" or empty): log_image_status image timestamp flag
log_image_status(){
  if [ "$3" = "applied" ]; then
    status="$(date +%Y%m%d%H%M) $3: $1 $2"
  else
    status="$2 created: $1 $2"
  fi
  echo "$status" | tee /tmp/image.status
  sendlog image.status
}

# mk_info imagename baseimagename device_size image_size - creates timestamp file
mk_info(){
  local timestamp=$(date +%Y%m%d%H%M)
  log_image_status $1 $timestamp
  echo "[$1 Info File]
timestamp=$timestamp
image=$1
baseimage=$2
partitionsize=$3
imagesize=$4"
}

# get_multicast_server file
get_multicast_server(){
  local file=""
  local serverport=""
  local relax=""
  while read file serverport relax; do
    if [ "$file" = "$1" ]; then
      echo "${serverport%%:*}"
      return 0
    fi
  done </multicast.list
  return 1
}

# get_multicast_port file
get_multicast_port(){
  local file=""
  local serverport=""
  local relax=""
  while read file serverport relax; do
    if [ "$file" = "$1" ]; then
      echo "${serverport##*:}"
      return 0
    fi
  done </multicast.list
  return 1
}

# download_multicast server port file
download_multicast(){
  local interface="$(route -n | grep ^0.0.0.0 | awk '{print $NF}')"
  echo "MULTICAST Download $interface($1):$2 -> $3"
  echo "udp-receiver --log /tmp/image.log --nosync --nokbd --interface $interface --rcvbuf 4194304 --portbase $2 --file $3"
  interruptible udp-receiver --log /tmp/image.log --nosync --nokbd --interface "$interface" --rcvbuf 4194304 --portbase "$2" --file "$3" 2>&1 ; RC="$?"
  return "$RC"
}

# torrent_watchdog image timeout
torrent_watchdog(){
  local image="$1"
  local complete="$image".complete
  local torrent="$image".torrent
  local logfile=/tmp/"$image".log
  local timeout="$2"
  local line=""
  local line_old=""
  local int=10
  local RC=1
  local c=0
  while [ $c -lt $timeout ]; do
    sleep $int
    # check if torrent is complete
    [ -e "$complete" ] && { RC=0; break; }
    line="$(tail -1 "$logfile" | awk '{ print $3 }')"
    [ -z "$line_old" ] && line_old="$line"
    if [ "$line_old" = "$line" ]; then
      [ $c -eq 0 ] || echo -e "\nTorrent-Watchdog: Download von $image stockt seit $c Sekunden." >&2 | tee -a /tmp/image.log
      c=$(($c+$int))
    else
      line_old="$line"
      c=0
    fi
  done
  rm -f "$logfile"
  echo
  if [ "$RC" = "0" ]; then
    echo "Image $image erfolgreich heruntergeladen." | tee -a /tmp/image.log
  else
    ps w | grep -v grep | grep -q ctorrent && killall -9 ctorrent
    echo "Download von $image wegen Zeitueberschreitung abgebrochen." >&2 | tee -a /tmp/image.log
  fi
  return "$RC"
}

# download_torrent image
download_torrent(){
  local image="$1"
  local torrent="$image".torrent
  local complete="$image".complete
  local RC=1
  [ -e "$torrent" ] || return "$RC"
  local ip="$(ip)"
  [ -z "$ip" -o "$ip" = "OFFLINE" ] && return "$RC"
  # default values
  local MAX_INITIATE=40
  local MAX_UPLOAD_RATE=0
  local SLICE_SIZE=128
  local TIMEOUT=300
  [ -e /torrent-client.conf ] && . /torrent-client.conf
  [ -n "$DOWNLOAD_SLICE_SIZE" ] && SLICE_SIZE=$(($DOWNLOAD_SLICE_SIZE/1024))
  local pid="$(ps w | grep ctorrent | grep "$torrent" | grep -v grep | awk '{ print $1 }')"
  [ -n "$pid" ] && kill "$pid"
  local OPTS="-I $ip -M $MAX_INITIATE -z $SLICE_SIZE"
  [ $MAX_UPLOAD_RATE -gt 0 ] && OPTS="$OPTS -U $MAX_UPLOAD_RATE"
  echo "Torrent-Optionen: $OPTS" >> /tmp/image.log
  echo "Starte Torrent-Dienst fuer $image." | tee -a /tmp/image.log
  local logfile=/tmp/"$image".log
  if [ ! -e "$complete" ]; then
    rm -f "$image" "$torrent".bf
    torrent_watchdog "$image" "$TIMEOUT" &
    interruptible ctorrent -e 0 $OPTS -X "touch $complete" "$torrent" | tee -a "$logfile"
  fi
  # start seeder if download is complete
  if [ -e "$complete" ]; then
    RC=0
    ctorrent -e 100000 $OPTS -f -d "$torrent"
  fi
  return "$RC"
}

# Download main file and supplementary files
# download_all server mainfile additional_files...
download_all(){
  local RC=0
  local server="$1"
  download "$server" "$2" important; RC="$?"
  if [ "$RC" != "0" ]; then
    rm -f "$2"
    return "$RC"
  fi
  shift; shift;
  local file=""
  for file in "$@"; do
    download "$server" "$file"
  done
  return "$RC"
}

# Download info files, compare timestamps
# download_if_newer server file downloadtype
download_if_newer(){
  # do not execute in localmode
  localmode && return 0
  # don't try empty filenames
  [ -z "$2" ] && return 0
  local DLTYPE="$3"
  [ -z "$DLTYPE" ] && DLTYPE="$(downloadtype)"
  [ -z "$DLTYPE" ] && DLTYPE="rsync"
  local RC=0
  local DOWNLOAD_ALL=""
  local IMAGE=""
  case "$2" in *.[Cc][Ll][Oo][Oo][Pp]|*.[Rr][Ss][Yy][Nn][Cc]) IMAGE="true" ;; esac
  if [ ! -s "$2" -o ! -s "$2".info ]; then # File not there, download all
    DOWNLOAD_ALL="true"
  else
    mv -f "$2".info "$2".info.old 2>/dev/null
    download "$1" "$2".info
    if [ -s "$2".info ]; then
      local ts1="$(getinfo "$2".info.old timestamp)"
      local ts2="$(getinfo "$2".info timestamp)"
      local fs1="$(getinfo "$2".info imagesize)"
      local fs2="$(get_filesize "$2")"
      if [ -n "$ts1" -a -n "$ts2" -a "$ts1" -gt "$ts2" ] >/dev/null 2>&1; then
        DOWNLOAD_ALL="true"
        echo "Server enthaelt eine neuere ($ts2) Version von $2 ($ts1)."
      elif  [ -n "$fs1" -a -n "$fs2" -a ! "$fs1" -eq "$fs2" ] >/dev/null 2>&1; then
        DOWNLOAD_ALL="true"
        echo "Dateigroesse von $2 ($fs1) im Cache ($fs2) stimmt nicht."
      fi
      rm -f "$2".info.old
    else
      DOWNLOAD_ALL="true"
      mv -f "$2".info.old "$2".info
    fi
  fi # File not there, download all
  # check for complete flag
  [ -z "$DOWNLOAD_ALL" -a -n "$IMAGE" -a ! -e "$2.complete" ] && DOWNLOAD_ALL="true"
  # supplemental torrent check
  if [ -n "$IMAGE" ]; then
    # issue 92, do this only if downloadtype is torrent
    if [ "$DLTYPE" = "torrent" ]; then
      # save local torrent file
      [ -e "$2.torrent" -a -z "$DOWNLOAD_ALL" ] && mv "$2".torrent "$2".torrent.old
      # download torrent file from server
      download_all "$1" "$2".torrent ; RC="$?"
      if [ "$RC" != "0" ]; then
        echo "Download von $2.torrent fehlgeschlagen." >&2
        return "$RC"
      fi
      # check for updated torrent file
      if [ -e "$2.torrent.old" ]; then
        cmp "$2".torrent "$2".torrent.old || DOWNLOAD_ALL="true"
        rm "$2".torrent.old
      fi
    fi # dltype torrent
    # update regpatch and postsync script
    rm -rf "$2".reg "$2".postsync
    download_all "$1" "$2".reg >/dev/null 2>&1
    download_all "$1" "$2".postsync >/dev/null 2>&1
  fi
  # start torrent service for others if there is no image to download
  [ "$DLTYPE" = "torrent" -a -n "$IMAGE" -a -z "$DOWNLOAD_ALL" ] && download_torrent "$2"
  # download because newer file exists on server
  if [ -n "$DOWNLOAD_ALL" ]; then
    if [ -n "$IMAGE" ]; then
      # remove complete flag
      rm -f "$2".complete
      # download images according to downloadtype torrent or multicast
      case "$DLTYPE" in
        torrent)
          # remove old image and torrents before download starts
          rm -f "$2" "$2".torrent.bf
          download_torrent "$2" ; RC="$?"
          [ "$RC" = "0" ] ||  echo "Download von $2 per Torrent fehlgeschlagen!" >&2
          ;;
        multicast)
          if [ -s /multicast.list ]; then
            local MPORT="$(get_multicast_port "$2")"
            if [ -n "$MPORT" ]; then
              download_multicast "$1" "$MPORT" "$2" ; RC="$?"
            else
              echo "Konnte Multicast-Port nicht bestimmen, kein Multicast-Download moeglich." >&2
              RC=1
            fi
          else
            echo "Datei multicast.list nicht gefunden, kein Multicast-Download moeglich." >&2
            RC=1
          fi
          [ "$RC" = "0" ] || echo "Download von $2 per multicast fehlgeschlagen!" >&2
          ;;
      esac
      # download per rsync also as a fallback if other download types failed
      if [ "$RC" != "0" -o "$DLTYPE" = "rsync" ]; then
        [ "$RC" = "0" ] || echo "Versuche Download per RSYNC." >&2
        download_all "$1" "$2" ; RC="$?"
        [ "$RC" = "0" ] || echo "Download von $2 per RSYNC fehlgeschlagen!" >&2
      fi
      # download supplemental files and set complete flag if image download was successful
      if [ "$RC" = "0" ]; then
        # reg und postsync were downloaded already above
        #download_all "$1" "$2".info "$2".desc "$2".reg "$2".postsync >/dev/null 2>&1
        download_all "$1" "$2".info "$2".desc >/dev/null 2>&1
        touch "$2".complete
      fi
    else # download other files than images
      download_all "$1" "$2" "$2".info ; RC="$?"
      [ "$RC" = "0" ] || echo "Download von $2 fehlgeschlagen!" >&2
    fi
  else # download nothing, no newer file on server
    echo "Keine neuere Version vorhanden, ueberspringe $2."
  fi
  return "$RC"
}

# Authenticate server user password share
authenticate(){
  local RC=1
  localmode ; RC="$?"
  if [ "$RC" = "1" ]; then
    export RSYNC_PASSWORD="$3"
    echo "Logge $2 ein auf $1..."
    rm -f "$TMP"
    rsync "$2@$1::linbo-upload" >/dev/null 2>"$TMP" ; RC="$?"
  elif [ -e /etc/linbo_passwd ]; then
    echo "Authentifiziere offline ..."
    md5passwd="$(echo -n "$3" | md5sum | awk '{ print $1 }')"
    linbo_md5passwd="$(cat /etc/linbo_passwd)"
    if [ "$md5passwd" = "$linbo_md5passwd" ]; then
      RC=0
    else
      echo 'Passt nicht!' >"$TMP" ; RC=1
    fi
  else
    echo 'Kann nicht lokal authentifizieren!' >"$TMP" ; RC=1
  fi
  if [ "$RC" != "0" ]; then
    echo "Fehler: $(cat "$TMP")" >&2
    echo "Falsches Passwort oder fehlende Passwortdatei?" >&2
  else
    echo "Passwort OK."
    # temporary workaround for password
    echo -n "$RSYNC_PASSWORD" > /tmp/linbo.passwd
  fi
  rm -f "$TMP"
  return "$RC"
}

# upload server user password cache file
upload(){
  # do not execute in localmode
  localmode && return 0
  local RC=0
  local file
  local ext
  if remote_cache "$4"; then
    echo "Cache $4 ist nicht lokal, die Datei $5 befindet sich" | tee -a /tmp/linbo.log
    echo "hoechstwahrscheinlich bereits auf dem Server, daher kein Upload." | tee -a /tmp/linbo.log
    sendlog
    return 1
  fi
  # We may need this password for mountcache as well!
  export RSYNC_PASSWORD="$3"
  mountcache "$4" || return "$?"
  cd /cache
  if [ -s "$5" ]; then
    local FILES="$5"
    # file list is obsolete
    #for ext in info postsync reg desc torrent; do
    for ext in info desc torrent; do
      [ -s "${5}.${ext}" ] && FILES="$FILES ${5}.${ext}"
    done
    echo "Lade $FILES auf $1 hoch ..." | tee -a /tmp/linbo.log
    for file in $FILES; do
      interruptible rsync --log-file=/tmp/rsync.log --progress -Ha $RSYNC_PERMISSIONS --partial "$file" "$2@$1::linbo-upload/$file"
      # because return code is always 0 this is necessary
      grep -q "rsync error" /tmp/rsync.log && RC=1
      cat /tmp/rsync.log >> /tmp/linbo.log
      rm /tmp/rsync.log
      if [ "$RC" != "0" ]; then
        break
      else
        # start torrent service for image
        case "$file" in
          *.torrent) ps w | grep ctorrent | grep "$file" | grep -v grep || download_torrent "${file%.torrent}" ;;
        esac
      fi
      #rm -f "$TMP"
    done
  else
    RC=1
    echo "Die Datei $5 existiert nicht, und kann daher nicht hochgeladen werden." | tee -a /tmp/linbo.log
  fi
  if [ "$RC" = "0" ]; then
    echo "Upload von $FILES nach $1 erfolgreich." | tee -a /tmp/linbo.log
  else
    echo "Upload von $FILES nach $1 ist fehlgeschlagen." | tee -a /tmp/linbo.log
  fi
  sendlog
  cd / ; mountcache "$4" -r
  [ "$RC" = "0" ] && echo "Upload von $FILES nach $1 erfolgreich." || echo "Upload von $FILES nach $1 ist fehlgeschlagen." >&2
  return "$RC"
}

# Sync from server
# syncr server cachedev baseimage image bootdev rootdev kernel initrd append [force]
syncr(){
  echo -n "syncr " ; printargs "$@"
  if remote_cache "$2"; then
    echo "Cache $2 ist nicht lokal, ueberspringe Aktualisierung der Images."
  else
    mountcache "$2" || return "$?"
    cd /cache
    local i
    for i in "$3" "$4"; do
      [ -n "$i" ] && download_if_newer "$1" "$i"
    done
    sendlog
    cd / ; mountcache "$2" -r
    # Also update LINBO, while we are here.
    update "$1" "$2"
  fi
  shift
  syncl "$@"
}

# update server cachedev force
# updates grub and linbo stuff
update(){
  # do not execute in localmode
  localmode && return 0

  echo -n "update " ;  printargs "$@"
  local doneflag="/tmp/.update.done"
  local force="$3"

  if [ -e "$doneflag" -a -z "$force" ]; then
    echo "LINBO-Update wurde schon ausgefuehrt!"
    return 0
  else
    rm -f "$doneflag"
  fi

  local rebootflag="/tmp/.linbo.reboot"
  local reboot
  local RC=0
  local group="$(hostgroup)"
  local server="$1"
  local cachelabel="$(print_partlabel "$2")"
  local cachedev="$(print_realdev "$cachelabel")"
  [ -z "$cachedev" ] && cachedev="$2"
  #local disk="${cachedev%%[1-9]*}"
  local grubdir="/cache/boot/grub"
  mountcache "$cachedev" || return 1
  mkdir -p "$grubdir"

  cd /cache
  local suffix="$(get_64)"
  # detect non pae cpu
  if [ -z "$suffix" ]; then
    grep ^flags /proc/cpuinfo | head -1 | grep -wq pae || suffix="-np"
  fi
  local kernel="linbo${suffix}"
  local kernelfs="linbofs${suffix}.lz"
  local md5_before
  local md5_after
  local md5_current
  local i
  local myname="$(clientname)"

  # local restore of start.conf in cache (necessary if cache partition was formatted before)
  [ -s start.conf ] || cp /start.conf .

  # check for linbo/linbofs updates on server
  # download newer linbo/linbofs if applicable and check download
  echo "Pruefe auf LINBO-Aktualisierungen."
  for i in "$kernel" "$kernelfs"; do
    md5_before="" ; md5_after="" ; md5_current=""
    [ -s "$i" ] && md5_before="$(md5sum "$i" | awk '{ print $1 }')"
    rm -f "${i}.md5"
    download "$server" "${i}.md5" && md5_after="$(cat "$i".md5 2> /dev/null)"
    if [ -z "$md5_after" ]; then
      echo "Download-Fehler bei ${i}.md5!" >&2
      rm -f "$kernel" "$kernelfs" "${kernel}.md5" "${kernelfs}.md5"
      return 1
    fi
    if [ -z "$md5_before" -o "$md5_before" != "$md5_after" ]; then
      download "$server" "$i" && md5_current="$(md5sum "$i" | awk '{ print $1 }')"
      if [ "$md5_after" = "$md5_current" ]; then
        echo "$i wurde erfolgreich aktualisiert."
        reboot="yes"
      else
        echo "Download-Fehler bei $i!" >&2
        rm -f "$kernel" "$kernelfs" "${kernel}.md5" "${kernelfs}.md5"
        return 1
      fi
    else
      echo "$i ist aktuell."
    fi
  done

  # get group specific and local grub configs from server
  echo "Aktualisiere GRUB-Konfiguration."
  for i in boot/grub/ipxe.lkrn "boot/grub/hostcfg/$myname.cfg" "boot/grub/$group.cfg" "boot/grub/spool/$myname.$group.grub.cfg"; do
    # collect md5 sums before download
    if [ "$i" = "boot/grub/hostcfg/$myname.cfg" ]; then
      md5_before="$(md5sum "$grubdir/custom.cfg" 2> /dev/null | awk '{ print $1 }')"
      hostcfg="yes"
    elif [ "$i" = "boot/grub/$group.cfg" -a -n "$hostcfg" ]; then
      # if download of hostcfg was successful skip download of groupcfg
      continue
    elif [ "$i" = "boot/grub/$group.cfg" -a -z "$hostcfg" ]; then
      md5_before="$(md5sum "$grubdir/custom.cfg" 2> /dev/null | awk '{ print $1 }')"
    elif [ "$i" = "boot/grub/spool/$myname.$group.grub.cfg" ]; then
      md5_before="$(md5sum "$grubdir/grub.cfg" 2> /dev/null | awk '{ print $1 }')"
    else
      md5_before="$(md5sum "$(basename "$i")" 2> /dev/null | awk '{ print $1 }')"
    fi
    RC=0
    download "$server" "$i" || RC=1
    if [ "$RC" = "1" ]; then
      # reset hostcfg if download failed
      if echo "$i" | grep -q hostcfg; then
        hostcfg=""
        rm -f "$i"
        continue
      fi
      echo "Download-Fehler bei $i!" >&2
      rm -f "$i"
      return 1
    fi
    # collect md5 after download
    md5_after="$(md5sum "$(basename "$i")" 2> /dev/null | awk '{ print $1 }')"
    # if md5 differ, reboot
    if [ "$md5_before" != "$md5_after" ]; then
      echo "$(basename "$i") wurde aktualisiert."
      reboot="yes"
    fi
  done

  # move downloads in place
  if [ -e "$myname.cfg" ]; then
    mv "$myname.cfg" "$grubdir/custom.cfg" || RC=1
    rm -f "$group.cfg"
  fi
  if [ -e "$group.cfg" ]; then
    mv "$group.cfg" "$grubdir/custom.cfg" || RC=1
  fi
  if [ -e "$myname.$group.grub.cfg" ]; then
    mv "$myname.$group.grub.cfg" "$grubdir/grub.cfg" || RC=1
  fi
  if [ "$RC" = "1" ]; then
    echo "Fehler beim Schreiben der GRUB-Konfigurationsdateien!" >&2
    return 1
  fi

  # keep grub themes also updated
  echo -n "Aktualisiere GRUB-Themes ... "
  themesdir="/boot/grub/themes"
  mkdir -p "/cache$themesdir"
  rsync -a --delete "${server}::linbo${themesdir}/" "/cache${themesdir}/" || RC=1
  if [ "$RC" = "1" ]; then
    echo "Fehler!" >&2
    return 1
  else
    echo "OK!"
  fi

  # fetch also current linuxmuster-win scripts
  echo -n "Aktualisiere linuxmuster-win ... "
  [ -d /cache/linuxmuster-win ] || mkdir -p /cache/linuxmuster-win
  rsync -a --exclude=*.ex --delete --delete-excluded "$server::linbo/linuxmuster-win/" /cache/linuxmuster-win/ || RC=1
  if [ "$RC" = "1" ]; then
    echo "Fehler!" >&2
    return 1
  else
    echo "OK!"
  fi

  # finally update/install grub stuff
  if mk_boot; then

    # set efi bootorder to local,network
    [ -d /sys/firmware/efi ] && set_efibootorder

    # remove for old legacy grub stuff
    if [ -e "$grubdir/stage1" -o -e "$grubdir/menu.lst" ]; then
      echo "Entferne GRUB legacy, Reboot wird notwendig."
      rm -f "$grubdir"/*stage* "$grubdir"/menu.lst gpxe.krn
      if [ -e /cache/update.log ]; then
        cat /cache/update.log >> /tmp/linbo.log
        sendlog
      fi
      ( umount -a ; /sbin/reboot -f )
    fi

  else
    RC="1"
  fi

  if [ "$RC" = "0" ]; then
    echo "LINBO/GRUB update fertig."
    touch "$doneflag"
    [ -n "$reboot" ] && touch "$rebootflag"
  else
    echo "Fehler!" >&2
  fi

  sendlog
  return "$RC"
}

# initcache server cachedev downloadtype images...
initcache(){
  # do not execute in localmode
  localmode && return 0
  echo -n "initcache " ;  printargs "$@"
  local server="$1"
  local cachedev="$2"
  local label="$(print_partlabel "$2")"
  local download_type="$3"
  local i
  local u
  local used_images
  local group
  local found
  if remote_cache "$cachedev"; then
    echo "Cache $cachedev ist nicht lokal, und muss daher nicht aktualisiert werden."
    return 1
  fi
  if [ -n "$FORCE_FORMAT" ]; then
    local cachefs="$(fstype_startconf "$cachedev")"
    if [ -n "$cachefs" ]; then
      echo "Formatiere Cache-Partition $cachedev..."
      format "$cachedev" "$cachefs" "$label" 2>> /tmp/linbo.log
    fi
  fi
  mountcache "$cachedev" || return "$?"
  cd /cache
  shift; shift; shift

  # clean up obsolete linbofs files
  rm -f linbofs[.a-zA-Z0-9_-]*.gz*
  rm -f linbo*.info

  # clean up obsolete image files
  used_images="$(grep -i ^baseimage /start.conf | awk -F\= '{ print $2 }' | awk '{ print $1 }')"
  used_images="$used_images $(grep -i ^image /start.conf | awk -F\= '{ print $2 }' | awk '{ print $1 }')"
  for i in *.cloop *.rsync; do
    [ -e "$i" ] || continue
    found=0
    for u in $used_images; do
      if [ "$i" = "$u" ]; then
        found=1
        break
      fi
    done
    if [ "$found" = "0" ]; then
      echo "Entferne nicht mehr benoetigte Imagedatei $i." | tee -a /tmp/image.log
      rm -f "$i" "$i".*
    fi
  done

  # update cache files
  for i in "$@"; do
    if [ -n "$i" ]; then
      download_if_newer "$server" "$i" "$download_type"
    fi
  done
  # obsolete, done in update() anyway
  #sendlog
  #cd / ; mountcache "$cachedev" -r
  update "$server" "$cachedev"
}

### Main ###
# DEBUG linbo_gui:
# echo -n "Running: $cmd "
# count=1
# for i in "$@"; do
#  echo -n "$((count++))=$i,"
# done
# echo ""
# sleep 1

# readfile cachepartition filename [destinationfile]
readfile(){
  local RC=0
  mountcache "$1" || return "$?"
  if [ -n "$3" ]; then
    cp -a /cache/"$2" "$3"
  else
    cat /cache/"$2"
  fi
  RC="$?"
  #sendlog
  #umount /cache
  return "$RC"
}

# writefile cachepartition filename [sourcefile]
writefile(){
  local RC=0
  mountcache "$1" -w || return "$?"
  if cache_writable; then
    if [ -n "$3" ]; then
      cp -a "$3" /cache/"$2"
    else
      cat > /cache/"$2"
    fi
    RC="$?"
  else
    echo "Cache ist nicht schreibbar, Datei $2 nicht gespeichert." >&2
    RC=1
  fi
  #sendlog
  mountcache "$1" -r
  return "$RC"
}

# ready - check if LINBO is ready (timeout 120 seconds)
ready(){
  # Files /tmp/linbo-network.done and /tmp/linbo-cache.done created by init.sh
  local count=0
  while [ ! -e /tmp/linbo-network.done -o ! -e /tmp/linbo-cache.done -o ! -s start.conf ]; do
    sleep 1
    #  echo -n "."
    count=`expr $count + 1`
    if [ "$count" -gt 120 ]; then
      echo "Zeitueberschreitung, LINBO noch nicht fertig. :-(" >&2
      return 1
    fi
  done
  localmode || echo "Netzwerk OK."
  echo "Lokale Festplatte(n) OK."
  return 0
}

mac(){
  local iface
  local mac
  iface="$(LANG=C route | grep ^default | awk '{ print $8 }' 2> /dev/null)"
  [ -n "$iface" ] && mac="$(LANG=C ifconfig "$iface" | grep HWaddr | awk '{print $5}' | tr a-z A-Z)"
  [ -z "$mac" ] && mac="OFFLINE"
  echo "$mac"
}

# Find all available batteries, get their capacity and output capacity of first found battery
battery(){
  find /sys/class/power_supply/ -name 'BAT*' -exec cat {}/capacity \; | head -n 1
}

# register server user password room hostname ip group role
register(){
  local RC=1
  local room="$4"
  local client="$5"
  local ip="$6"
  local group="$7"
  local role="$8"
  [ -z "$role" ] && role="classroom-studentcomputer"
  local macaddr="$(mac)"
  [ "$maccaddr" = "OFFLINE" ] && return 1
  local info="$room;$client;$group;$macaddr;$ip;;;;$role;;1"
  # Plausibility check
  if echo "$client" | grep -qi '[^a-z0-9-]'; then
    echo "Falscher Rechnername: '$client'," >&2
    echo "Rechnernamen duerfen nur Buchstaben [a-z0-9-] enthalten." >&2
    return 1
  fi
  if echo "$group" | grep -qi '[^a-z0-9_]'; then
    echo "Falscher Gruppenname: '$group'," >&2
    echo "Rechnergruppen duerfen nur Buchstaben [a-z0-9_] enthalten." >&2
    return 1
  fi
  cd /tmp
  echo "$info" '>' "$client.new"
  echo "$info" >"$client.new"
  echo "Uploade $client.new auf $1..."
  export RSYNC_PASSWORD="$3"
  interruptible rsync --progress -HaP "$client.new" "$2@$1::linbo-upload/$client.new" ; RC="$?"
  cd /
  return "$RC"
}

ip(){
  local iface
  local ip
  iface="$(LANG=C route | grep ^default | awk '{ print $8 }' 2> /dev/null)"
  [ -n "$iface" ] && ip="$(LANG=C ifconfig "$iface" | grep 'inet addr:' | awk -F\: '{ print $2 }' | awk '{ print $1 }')"
  [ -z "$ip" ] && ip="OFFLINE"
  echo "$ip"
}

netmask(){
  [ -e /tmp/dhcp.log ] || return 0
  grep ^subnet /tmp/dhcp.log | awk -F\' '{print $2}' | head -1
}

bitmask(){
  local netmask="$(netmask)"
  [ -z "$netmask" ] && return 0
  ipcalc -p "$netmask" | awk -F\= '{print $2}'
}

clientname(){
  if [ -s /tmp/network.ok ]; then
    source /tmp/network.ok
    echo "$hostname"
    return
  fi
  local device="$(cachedev)"
  if [ -b "$device" ]; then
    if mountcache $device -r &> /dev/null; then
      if [ -s /cache/hostname ]; then
        awk -F\. '{ print $1 }' /cache/hostname
        return
      fi
    fi
  fi
  hostname
}

cpu(){
  cat /proc/cpuinfo | grep name | sed 's,model.*:\ ,,'
}

memory(){
  free | grep Mem | awk '{printf "%d MB\n",$2 / 1024}'
}

size(){
  if mountpart "$1" /mnt -r 2>> /tmp/linbo.log; then
    df -k /mnt 2>/dev/null | tail -1 | \
      awk '{printf "%.1f/%.1fGB\n", $4 / 1048576, $2 / 1048576}' 2>/dev/null
    umount /mnt
  else
    local d=$(get_partition_size "$1")
    if [ "$?" = "0" -a "$d" -ge 0 ] 2>/dev/null; then
      echo "$d" | awk '{printf "%.1fGB\n",$1 / 1048576}' 2>/dev/null
    else
      echo " -- "
    fi
  fi
  return 0
}

# list cloop images in cache, listimages cachedev
listimages(){
  mountcache "$1" -r || return 1
  ls -1 /cache/*.cloop || return 1
}

#
# jweiher, angepasst tschmitt
# Ermittelt den logisch naechsten Hostnamen, um die Rechneraufnahme zu
# erleichtern
#
preregister() {
  local LAST_REGISTERED="/tmp/last_registered"
  interruptible rsync --progress -HaP "$1::linbo/last_registered" "$LAST_REGISTERED" ; RC="$?"
  local LASTWORKSTATION="$(grep ^[a-z0-9] "$LAST_REGISTERED" | tail -n 1)"

  if [ "$LASTWORKSTATION" == "" ]; then
    echo ",,," > /tmp/newregister
    rm -f "$LAST_REGISTERED"
    return 0
  fi

  local LASTGROUP="$(echo $LASTWORKSTATION | cut -d ";" -f 3)"
  local LASTROOM="$(echo $LASTWORKSTATION | cut -d ";" -f 1)"
  local LASTHOST="$(echo $LASTWORKSTATION | cut -d ";" -f 2)"
  local LASTIP="$(echo $LASTWORKSTATION | cut -d ";" -f 5)"

  # Naechste IP ermitteln
  local NEXTIP="$(echo -n $LASTIP | cut -d "." -f 1-3).$(($(echo $LASTIP | cut -d "." -f 4)+1))"

  # Naechsten Hostnamen ermitteln
  local HOSTNAMECOUNTER="$(echo $LASTHOST | grep -Eo "[0-9]+$")"
  local NEXTCOUNT
  if [ ! "$HOSTNAMECOUNTER" == "" ]; then
    NEXTCOUNT=$(expr $HOSTNAMECOUNTER + 1)
    # Left fill with zeroes
    while [ "${#NEXTCOUNT}" -lt "${#HOSTNAMECOUNTER}" ]; do
      NEXTCOUNT=0$NEXTCOUNT
    done

    # Build new hostname
    local NEXTHOST="$(echo -n $LASTHOST | sed "s/${HOSTNAMECOUNTER}$//g")$NEXTCOUNT"
  else
    NEXTHOST="$LASTHOST"
  fi
  rm -f "$LAST_REGISTERED"
  echo "$LASTROOM,$LASTGROUP,$NEXTHOST,$NEXTIP" > /tmp/newregister
  return 0
}

version(){
  local versionfile="/etc/linbo-version"
  if [ -s "$versionfile" ]; then
    cat "$versionfile"
  else
    echo "LINBO 2.x"
  fi
}

# Main

case "$cmd" in
  ip) ip ;;
  netmask) netmask ;;
  bitmask) bitmask ;;
  hostname) clientname ;;
  cpu) cpu ;;
  memory) memory ;;
  mac) mac ;;
  battery) battery ;;
  size) size "$@" ;;
  listimages) listimages "$@" ;;
  authenticate) authenticate "$@" ;;
  create) create "$@" ;;
  start) start "$@" ;;
  label) label_allpartitions ;;
  partition_noformat) export NOFORMAT=1; partition "$@" ;;
  partition) partition "$@" ;;
  preregister) preregister "$@" ;;
  download) download "$@" ;;
  initcache) initcache "$@" ;;
  initcache_format) echo "initcache_format gestartet."; export FORCE_FORMAT=1; initcache "$@" ;;
  mountcache) mountcache "$@" ;;
  mount) mountpart "$@" ;;
  fstype) fstype "$@" ;;
  readfile) readfile "$@" ;;
  ready) ready "$@" ;;
  register) register "$@" ;;
  sync) syncl "$@" && { cache="$1"; shift 3; start "$1" "$2" "$3" "$4" "$5" "$cache"; } ;;
  syncstart) syncr "$@" && { cache="$2"; shift 4; start "$1" "$2" "$3" "$4" "$5" "$cache"; } ;;
  syncr) syncr "$@" && { cache="$2"; shift 4; start "$1" "$2" "$3" "$4" "$5" "$cache"; } ;;
  synconly) syncr "$@" ;;
  update) update "$@" ;;
  upload) upload "$@" ;;
  version) version ;;
  writefile) writefile "$@" ;;
  *) help "$cmd" "$@" ;;
esac

# Return returncode
exit "$?"
