#!/bin/sh

################################################################################
#      This file is part of OpenELEC - http://www.openelec.tv
#      Copyright (C) 2009-2014 Stephan Raue (stephan@openelec.tv)
#      Copyright (C) 2010-2011 Roman Weber (roman@openelec.tv)
#      Copyright (C) 2012 Yann Cézard (eesprit@free.fr)
#
#  OpenELEC is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 2 of the License, or
#  (at your option) any later version.
#
#  OpenELEC is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with OpenELEC.  If not, see <http://www.gnu.org/licenses/>.
################################################################################

  # create directories
  /bin/busybox mkdir -p /dev
  /bin/busybox mkdir -p /proc
  /bin/busybox mkdir -p /sys
  /bin/busybox mkdir -p /flash
  /bin/busybox mkdir -p /sysroot
  /bin/busybox mkdir -p /storage

  # mount all needed special filesystems
  /bin/busybox mount -t devtmpfs devtmpfs /dev
  /bin/busybox mount -t proc proc /proc
  /bin/busybox mount -t sysfs sysfs /sys

  # set needed variables
  MODULE_DIR=/lib/modules

  UPDATE_DIR=/storage/.update
  UPDATE_KERNEL="KERNEL"
  UPDATE_SYSTEM="SYSTEM"
  IMAGE_KERNEL="KERNEL"
  IMAGE_SYSTEM="SYSTEM"

  BOOT_STEP="start"
  MD5_FAILED="0"
  MD5_NOCHECK="0"
  SIZE_FAILED="0"
  RUN_FSCK="yes"
  RUN_FSCK_DISKS=""

  NBD_DEVS="0"
  FLASH_FREE_MIN="5"

  INSTALLED_MEMORY=`cat /proc/meminfo | grep 'MemTotal:' | awk '{print $2}'`
  SYSTEM_TORAM_LIMIT=1024000

  # hide kernel log messages on console
  echo '1 4 1 7' > /proc/sys/kernel/printk

  # clear screen and hide cursor
  clear
  echo 0 > /sys/devices/virtual/graphics/fbcon/cursor_blink

  # parse command line arguments
  for arg in $(cat /proc/cmdline); do
    case $arg in
      BOOT_IMAGE=*)
        IMAGE_KERNEL="${arg#*=}"
        ;;
      SYSTEM_IMAGE=*)
        IMAGE_SYSTEM="${arg#*=}"
        ;;
      boot=*)
        boot="${arg#*=}"
        case $boot in
          ISCSI=*|NBD=*|NFS=*)
            UPDATE_DISABLED=yes
            FLASH_NETBOOT=yes
            ;;
          /dev/*|LABEL=*|UUID=*)
            RUN_FSCK_DISKS="$RUN_FSCK_DISKS $boot"
            ;;
        esac
        ;;
      disk=*)
        disk="${arg#*=}"
        case $disk in
          ISCSI=*|NBD=*|NFS=*)
            STORAGE_NETBOOT=yes
            ;;
          /dev/*|LABEL=*|UUID=*)
            RUN_FSCK_DISKS="$RUN_FSCK_DISKS $disk"
            ;;
        esac
        ;;
      wol_mac=*)
        wol_mac="${arg#*=}"
        ;;
      wol_wait=*)
        wol_wait="${arg#*=}"
        ;;
      textmode)
        INIT_ARGS="$INIT_ARGS --unit=textmode.target"
        ;;
      installer)
        INIT_ARGS="$INIT_ARGS --unit=installer.target"
        ;;
      debugging)
        DEBUG=yes
        ;;
      progress)
        PROGRESS=yes
        INIT_ARGS="$INIT_ARGS --show-status=1"
        ;;
      nofsck)
        RUN_FSCK=no
        ;;
      nosplash)
        SPLASH=no
        ;;
      noram)
        SYSTEM_TORAM=no
        ;;
      overlay)
        OVERLAY=yes
        ;;
      break=*)
        BREAK="${arg#*=}"
        ;;
    esac
  done

  if test "$DEBUG" = "yes"; then
    exec 3>&1
  else
    exec 3>/dev/null
  fi
  SILENT_OUT=3

  progress() {
    if test "$PROGRESS" = "yes"; then
      echo "### $1 ###"
    fi
  }

  debug_shell() {
    echo "### Starting debugging shell... type  exit  to quit ###"

    # show cursor
    echo 0 > /sys/devices/virtual/graphics/fbcon/cursor_blink

    sh </dev/tty1 >/dev/tty1 2>&1
  }

  error() {
    # Display fatal error message
    # $1:action which caused error, $2:message
    echo "*** Error in $BOOT_STEP: $1: $2 ***"
    debug_shell
  }

  break_after() {
    # Start debug shell after boot step $1
    case $BREAK in
      all|*$1*)
        debug_shell
        ;;
    esac
  }

  # Mount handlers
  # All handlers take the following parameters:
  # $1:target, $2:mountpoint, $3:mount options, [$4:fs type]

  mount_common() {
    # Common mount handler, handles block devices and filesystem images
    MOUNT_OPTIONS="-o $3"
    [ -n "$4" ] && MOUNT_OPTIONS="-t $4 $MOUNT_OPTIONS"

    for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
      ERR_ENV=1

      mount $MOUNT_OPTIONS $1 $2 >&$SILENT_OUT 2>&1
      [ "$?" -eq "0" ] && ERR_ENV=0 && break

      usleep 1000000
    done
    [ "$ERR_ENV" -ne "0" ] && error "mount_common" "Could not mount $1"
  }

  get_iscsistart_options() {
    # Convert kernel commandline ISCSI= options to iscsistart options
    IFS_SAVE="$IFS"
    IFS=,

    for arg in $1; do
      val="${arg#*=}"
      case "$arg" in
        iscsi_initiator=*)
          option="-i"
          ;;
        iscsi_target_name=*)
          option="-t"
          ;;
        iscsi_target_ip=*)
          option="-a"
          ;;
        iscsi_target_port=*)
          option="-p"
          ;;
        iscsi_target_group=*)
          option="-g"
          ;;
        iscsi_username=*)
          option="-u"
          ;;
        iscsi_password=*)
          option="-w"
          ;;
        iscsi_in_username=*)
          option="-U"
          ;;
        iscsi_in_password=*)
          option="-W"
          ;;
      esac
      echo "$option $val"
    done

    IFS="$IFS_SAVE"
  }

  mount_iscsi() {
    # Mount iSCSI target
    ISCSI_DEV="${1##*,}"
    ISCSI_OPTIONS="${1%,*}"

    if [ ! -f "/sbin/iscsistart" ]; then
      error "iscsistart" "iSCSI support not available"
    fi

    if [ "$ISCSI_OPTIONS" = "auto" ]; then
      progress "Network configuration based on iBFT"
      /sbin/iscsistart -N >&$SILENT_OUT 2>&1 || \
        error "iscsistart" "Unable to configure network"
      progress "iSCSI auto connect based on iBFT"
      /sbin/iscsistart -b >&$SILENT_OUT 2>&1 || \
        error "iscsistart" "Unable to auto connect"
    else
      /sbin/iscsistart $(get_iscsistart_options "$ISCSI_OPTIONS") >&$SILENT_OUT 2>&1 || \
        error "iscsistart" "Unable to connect to ISCSI target"
    fi

    mount_common "$ISCSI_DEV" "$2" "$3" "$4"
  }

  mount_nbd() {
  # Mount NBD device
    NBD_SERVER="${1%%:*}"
    NBD_PORT="${1#*:}"
    NBD_DEV="/dev/nbd$NBD_DEVS"

    nbd-client $NBD_SERVER $NBD_PORT $NBD_DEV >&$SILENT_OUT 2>&1 || \
      error "nbd-client" "Could not connect to NBD server $1"

    mount_common "$NBD_DEV" "$2" "$3" "$4"

    NBD_DEVS=$(( ${NBD_DEVS} + 1 ))
  }

  mount_nfs() {
    # Mount NFS export
    NFS_EXPORT="${1%%,*}"
    NFS_OPTIONS="${1#*,}"

    [ "$NFS_OPTIONS" = "$1" ] && NFS_OPTIONS=

    mount_common "$NFS_EXPORT" "$2" "$3,nolock,soft,timeo=3,retrans=2,rsize=32768,wsize=32768,$NFS_OPTIONS" "nfs"
  }

  mount_part() {
    # Mount a local or network filesystem
    # $1:[TYPE=]target, $2:mountpoint, $3:mount options, [$4:fs type]
    progress "mount filesystem $1 ..."

    MOUNT_TARGET="${1#*=}"
    case $1 in
      LABEL=*|UUID=*|/*)
        MOUNT_CMD="mount_common"
        MOUNT_TARGET="$1"
        ;;
      ISCSI=*)
        MOUNT_CMD="mount_iscsi"
        ;;
      NBD=*)
        MOUNT_CMD="mount_nbd"
        ;;
      NFS=*)
        MOUNT_CMD="mount_nfs"
        ;;
      *)
        error "mount_part" "Unknown filesystem $1"
        ;;
    esac
    $MOUNT_CMD "$MOUNT_TARGET" "$2" "$3" "$4"
  }

  update() {
    if [ -f "$UPDATE_DIR/$2" -a -f "$3" ]; then
      echo "updating $1..."
      mount -o remount,rw /flash
      mv $UPDATE_DIR/$2 $3 2>/dev/null
      # loopback file needs writable /flash all the time
      if [ "${disk%%=*}" != "FILE" ]; then
        mount -o remount,ro /flash
      fi
      sync
    fi
  }

  update_bootloader() {
    export BOOT_ROOT="/flash"
    export SYSTEM_ROOT="/sysroot"

    mount_part "/flash/$IMAGE_SYSTEM" "/sysroot" "ro,loop"

    if [ -f $SYSTEM_ROOT/usr/share/bootloader/update.sh ]; then
      echo "updating Bootloader..."
      sh $SYSTEM_ROOT/usr/share/bootloader/update.sh
      sync
    fi

    umount /sysroot
  }

  load_modules() {
    progress "Loading kernel modules"

    [ ! -f "/etc/modules" ] && return
    for module in $(cat /etc/modules); do
      progress "Loading kernel module $module"
      insmod "$MODULE_DIR/$module.ko" || \
        progress "... Failed to load kernel module $module, skipping"
    done
  }

  load_splash() {
    if [ ! "$SPLASH" = "no" ]; then
      progress "Loading bootsplash"

      SPLASHIMAGE="/splash/splash-full.png"

      # load uvesafb module if needed
      if [ -f "$MODULE_DIR/uvesafb.ko" -a ! -e /dev/fb0 ]; then
        progress "Loading kernel module uvesafb.ko"
        insmod "$MODULE_DIR/uvesafb.ko" || \
          progress "... Failed to load kernel module uvesafb, skipping"

        # set framebuffer to a default resolution (1024x768-32)
        if [ ! "$SWITCH_FRAMEBUFFER" = "no" ]; then
          fbset -g 1024 768 1024 768 32
          SPLASHIMAGE="/splash/splash-1024.png"
        fi
      fi

      if [ -e /dev/fb0 ]; then
        # load splash
        if [ -f /flash/oemsplash.png ]; then
          SPLASHIMAGE="/flash/oemsplash.png"
        elif [ -f /splash/splash.conf ]; then
          . /splash/splash.conf
        fi

        ply-image $SPLASHIMAGE > /dev/null 2>&1
      fi
    fi
  }

  do_reboot() {
    echo "System reboots now..."

    # syncing filesystem
      sync

    # unmount filesystems
      if /bin/busybox mountpoint -q /flash ; then
         /bin/busybox umount /flash
      fi

      if /bin/busybox mountpoint -q /storage ; then
        /bin/busybox umount /storage
      fi

    usleep 2000000
    /bin/busybox reboot
  }

  check_disks() {
    if [ "$RUN_FSCK" = "yes" -a -n "$RUN_FSCK_DISKS" ]; then
      progress "Checking disk(s): $RUN_FSCK_DISKS"
        /sbin/fsck -T -M -p -a $RUN_FSCK_DISKS > /dev/null 2>&1
        FSCK_RET="$?"

        case "$FSCK_RET" in
          0) # no errors found
            progress "no filesystem errors found, continuing..."
            ;;
          1) # filesystem errors corrected
            progress "filesystem errors corrected , continuing..."
            ;;
          2) # reboot needed
            echo "filesystem repaired, reboot needed..."
            do_reboot
            ;;
          4) # errors left
            error "could not repair filesystem, dropping to debug shell, try to run 'fsck' manually"
            do_reboot
            ;;
        esac
    fi
  }

  wakeonlan() {
    if [ "$STORAGE_NETBOOT" = "yes" ]; then
      wol_ip=${disk%:*}
      wol_ip=${wol_ip#*=}
    elif [ "$FLASH_NETBOOT" = "yes" ]; then
      wol_ip=${boot%:*}
      wol_ip=${wol_ip#*=}
    else
      return 0
    fi

    if [ -n "$wol_ip" -a -n "$wol_mac" -a -n "$wol_wait" ]; then
      progress "Sending Magic Packet (WOL) if needed"

      if ! ping -q -c 2 "$wol_ip" &>/dev/null; then
        ether-wake "$wol_mac"
        sleep "$wol_wait"
      fi
    fi
  }

  mount_flash() {
    progress "Mounting flash"

    wakeonlan

    mount_part "$boot" "/flash" "ro,noatime"
  }

  mount_storage() {
    progress "Mounting storage"

    wakeonlan

    if [ -n "$disk" ]; then
      if [ -n "$OVERLAY" ]; then
        OVERLAY_DIR=`cat /sys/class/net/eth0/address | tr -d :`

        mount_part "$disk" "/storage" "rw,noatime"
        mkdir -p /storage/$OVERLAY_DIR
        umount /storage

        # split $disk into $target,$options so we can append $OVERLAY_DIR
        options="${disk#*,}"
        target="${disk%%,*}"
        if [ "$options" = "$disk" ]; then
          disk="$target/$OVERLAY_DIR"
        else
          disk="$target/$OVERLAY_DIR,$options"
        fi
      fi
      mount_part "$disk" "/storage" "rw,noatime"
    fi
  }

  check_update() {
    progress "Checking for updates"

    # check for ATV1 mach-o-kernel
    if [ -f "/flash/com.apple.Boot.plist" -a -f "/flash/boot.efi" ]; then
      UPDATE_KERNEL="MACH_KERNEL"
    fi

    UPDATE_TAR=`ls -1 "$UPDATE_DIR"/*.tar 2>/dev/null | head -n 1`
    if [ -f "$UPDATE_DIR/$UPDATE_KERNEL" -a -f "$UPDATE_DIR/$UPDATE_SYSTEM" -o -f "$UPDATE_TAR" ] ; then
      if [ "$UPDATE_DISABLED" = "yes" ] ; then
        rm -rf $UPDATE_DIR/[0-9a-zA-Z]* &>/dev/null
        echo "Updating not supported on netboot. normal startup in 10s..."
        sync
        usleep 10000000
        return 0
      fi

      # check for .tar
      if [ -f "$UPDATE_TAR" ] ; then
        echo "Found new .tar archive. extracting..."
        mkdir -p $UPDATE_DIR/.tmp &>/dev/null
        tar -xf "$UPDATE_TAR" -C $UPDATE_DIR/.tmp &>/dev/null
        mv $UPDATE_DIR/.tmp/*/target/* $UPDATE_DIR &>/dev/null
        rm -f "$UPDATE_TAR" &>/dev/null
        rm -rf $UPDATE_DIR/.tmp &>/dev/null
        sync
        if [ ! -f "$UPDATE_DIR/$UPDATE_KERNEL" -o ! -f "$UPDATE_DIR/$UPDATE_SYSTEM" ] ; then
          echo "missing ${UPDATE_KERNEL} or ${UPDATE_SYSTEM}... normal startup in 10s"
          sync
          usleep 10000000
          return 0
        fi
      fi

      if [ -f "$UPDATE_DIR/.nocheck" ] ; then
        MD5_NOCHECK="1"
      fi

      # check md5 sums if .nocheck doesn't exist
      if [ "$MD5_NOCHECK" -eq "0" ] ; then
        if [ -f "$UPDATE_DIR/${UPDATE_KERNEL}.md5" -a -f "$UPDATE_DIR/${UPDATE_SYSTEM}.md5" ] ; then
          # *.md5 size-check
          if [ ! -s "$UPDATE_DIR/${UPDATE_KERNEL}.md5" -o ! -s "$UPDATE_DIR/${UPDATE_SYSTEM}.md5" ] ; then
            echo "zero-sized .md5 file..."
            MD5_FAILED="1"
          else
            sed  -i 's#target#/storage/.update#g' "$UPDATE_DIR/${UPDATE_KERNEL}.md5"
            sed  -i 's#target#/storage/.update#g' "$UPDATE_DIR/${UPDATE_SYSTEM}.md5"

            echo "Checking ${UPDATE_KERNEL}.md5..."
            md5sum -c "$UPDATE_DIR/${UPDATE_KERNEL}.md5" || MD5_FAILED="1"

            echo "Checking ${UPDATE_SYSTEM}.md5..."
            md5sum -c "$UPDATE_DIR/${UPDATE_SYSTEM}.md5" || MD5_FAILED="1"
          fi
        else
          echo "missing ${UPDATE_KERNEL}.md5 or ${UPDATE_SYSTEM}.md5..."
          MD5_FAILED="1"
        fi
      fi

      # get sizes
      FLASH_FREE=$(df /flash/ | awk '/[0-9]%/{print $4}')
      FLASH_FREE=$(( $FLASH_FREE * 1024 ))
      OLD_KERNEL=$(stat -t "/flash/$IMAGE_KERNEL" | awk '{print $2}')
      OLD_SYSTEM=$(stat -t "/flash/$IMAGE_SYSTEM" | awk '{print $2}')
      NEW_KERNEL=$(stat -t "$UPDATE_DIR/$UPDATE_KERNEL" | awk '{print $2}')
      NEW_SYSTEM=$(stat -t "$UPDATE_DIR/$UPDATE_SYSTEM" | awk '{print $2}')

      # old KERNEL+SYSTEM+free space - new KERNEL+SYSTEM must be higher then 5MB
      # at least 5MB free after update

      TMP_SIZE=$(($OLD_KERNEL+$OLD_SYSTEM+$FLASH_FREE-$NEW_KERNEL-$NEW_SYSTEM))
      FLASH_FREE_MIN=$(($FLASH_FREE_MIN*1024*1024))

      if [ $TMP_SIZE -ge $FLASH_FREE_MIN ]; then
        echo "Checking size: OK"
      else
        echo "Checking size: FAILED"
        SIZE_FAILED="1"
      fi

      # update if size check is ok
      if [ "$SIZE_FAILED" -eq "0" ] ; then
        # update if md5 check is ok or .nocheck exists
        if [ "$MD5_FAILED" -eq "0" -o "$MD5_NOCHECK" -eq "1" ] ; then
          update "Kernel" "$UPDATE_KERNEL" "/flash/$IMAGE_KERNEL"
          update "System" "$UPDATE_SYSTEM" "/flash/$IMAGE_SYSTEM"
          update_bootloader
          rm -rf $UPDATE_DIR/[0-9a-zA-Z]* &>/dev/null
          do_reboot
        else
          rm -rf $UPDATE_DIR/[0-9a-zA-Z]* &>/dev/null
          echo "md5 check failed. normal startup in 30s..."
          sync
          usleep 30000000
        fi
      else
        rm -rf $UPDATE_DIR/[0-9a-zA-Z]* &>/dev/null
        echo "size check failed. normal startup in 30s..."
        sync
        usleep 30000000
      fi
    fi
  }

  prepare_sysroot() {
    progress "Preparing system"

    if [ "$SYSTEM_TORAM" = "no" -o "$INSTALLED_MEMORY" -lt "$SYSTEM_TORAM_LIMIT" ]; then
      mount_part "/flash/$IMAGE_SYSTEM" "/sysroot" "ro,loop"
    else
      cp /flash/$IMAGE_SYSTEM /dev/$IMAGE_SYSTEM
      mount_part "/dev/$IMAGE_SYSTEM" "/sysroot" "ro,loop"
    fi

    mount --move /flash /sysroot/flash

    if [ -n "$disk" ]; then
      mount --move /storage /sysroot/storage
    fi

    [ -f "/sysroot/usr/lib/systemd/systemd" ] || error "final_check" "Could not find system."
  }

  if [ "${boot%%=*}" = "FILE" ]; then
    error "check arguments" "boot argument can't be FILE type..."
  fi

  # main boot sequence
  for BOOT_STEP in \
      load_modules \
      check_disks \
      mount_flash \
      load_splash \
      mount_storage \
      check_update \
      prepare_sysroot; do
    $BOOT_STEP
    [ -n "$DEBUG" ] && break_after $BOOT_STEP
  done

  BOOT_STEP=final

  # no suspend when booted from nonpersistent storage
  STORAGE=$(cat /proc/mounts | grep " /sysroot/storage " | awk '{print $1}' | awk -F '/' '{print $3}')
  if [ -n "$STORAGE" ] ; then
    removable="/sys/class/block/*/$STORAGE/../removable"
    if [ -e $removable ] ; then
      if [ "$(cat $removable 2>/dev/null)" = "1" ] ; then
        SUSPEND_DISABLED=yes
      fi
    fi
  fi
  FLASH=$(cat /proc/mounts | grep " /sysroot/flash " | awk '{print $1}' | awk -F '/' '{print $3}')
  if [ -n "$FLASH" ] ; then
    removable="/sys/class/block/*/$FLASH/../removable"
    if [ -e $removable ] ; then
      if [ "$(cat $removable 2>/dev/null)" = "1" ] ; then
        SUSPEND_DISABLED=yes
      fi
    fi
  fi
  # move some special filesystems
  /bin/busybox mount --move /dev /sysroot/dev
  /bin/busybox mount --move /proc /sysroot/proc
  /bin/busybox mount --move /sys /sysroot/sys

  # tell OE settings addon to disable updates
  if [ "$UPDATE_DISABLED" = "yes" ] ; then
    echo "" > /sysroot/dev/.update_disabled
  fi
  # swap can not be used over nfs.(see scripts/mount-swap)
  if [ "$STORAGE_NETBOOT" = "yes" ] ; then
    echo "" > /sysroot/dev/.storage_netboot
  fi
  # no suspend when booted from nonpersistent storage
  if [ "$SUSPEND_DISABLED" = "yes" ] ; then
    echo "" > /sysroot/dev/.suspend_disabled
  fi
  # switch to new sysroot and start real init
  exec /bin/busybox switch_root /sysroot /usr/lib/systemd/systemd $INIT_ARGS

  error "switch_root" "Error in initramfs. Could not switch to new root"
