#!/bin/sh

################################################################################
#      This file is part of OpenELEC - http://www.openelec.tv
#      Copyright (C) 2009-2012 Stephan Raue (stephan@openelec.tv)
#      Copyright (C) 2010-2011 Roman Weber (roman@openelec.tv)
#      Copyright (C) 2012 Yann Cézard (eesprit@free.fr)
#
#  This Program 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, or (at your option)
#  any later version.
#
#  This Program 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.tv; see the file COPYING.  If not, write to
#  the Free Software Foundation, 51 Franklin Street, Suite 500, Boston, MA 02110, USA.
#  http://www.gnu.org/copyleft/gpl.html
################################################################################

MODULE_DIR=/lib/modules

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

REBOOT="0"
MD5_FAILED="0"
MD5_NOCHECK="0"

NBD_DEVS="0"

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

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

# clear screen and hide cursor
  /bin/busybox 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#*=}"
        ;;
      disk=*)
        disk="${arg#*=}"
        ;;
      debugging)
        DEBUG=yes
        ;;
      bootchart)
        BOOTCHART=yes
        ;;
      ssh)
        SSH=yes
        ;;
      progress)
        PROGRESS=yes
        ;;
      fastboot)
        FASTBOOT=yes
        ;;
      nosplash)
        SPLASH=no
        ;;
      noram)
        SYSTEM_TORAM=no
        ;;
      overlay)
        OVERLAY=yes
        ;;
      break=*)
        BREAK="${arg#*=}"
        ;;
    esac
  done

  if test "$FASTBOOT" = "yes"; then
    IONICE="/bin/busybox ionice -c 1 -n 0"
  fi

  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

    /bin/busybox 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 ***"
    if [ -z "$DEBUG" ]; then
      /bin/busybox halt
    else
      debug_shell
    fi
  }

  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

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

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

  mount_cifs() {
  # Mount CIFS (Samba) share
    CIFS_SHARE="${1%%,*}"
    CIFS_OPTIONS="${1#*,}"
    [ "$CIFS_OPTIONS" = "$1" ] && CIFS_OPTIONS=

    mount_common "$CIFS_SHARE" "$2" "$3,$CIFS_OPTIONS" "cifs"
  }

  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"

    $IONICE /bin/busybox 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,retrans=10,$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"
        ;;
      CIFS=*|SMB=*)
        MOUNT_CMD="mount_cifs"
        ;;
      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..."
        $IONICE /bin/busybox mount -o remount,rw /flash
        $IONICE /bin/busybox mv $UPDATE_DIR/$2 $3
        $IONICE /bin/busybox mount -o remount,ro /flash
        $IONICE /bin/busybox sync
    fi
  }

  update_bootloader() {
    if [ -f "/flash/$IMAGE_SYSTEM" ]; then
    # /flash is filesystem with system image file
      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..."
          $IONICE /bin/busybox sh $SYSTEM_ROOT/usr/share/bootloader/update.sh
          $IONICE /bin/busybox sync
      fi

      umount /sysroot
    fi
  }


  hfsdiskprep() {
    for DEVICE in /dev/sd*; do
      for device in $(/bin/busybox blkid $DEVICE); do
        case $device in
          TYPE=*)
            FS_TYPE=${device#TYPE=}
            ;;
        esac
      done

      if [ "$FS_TYPE" = "\"hfs\"" -o "$FS_TYPE" = "\"hfsplus\"" ]; then
        progress "check filesystem $DEVICE [$FS_TYPE]..."
        /bin/fsck_hfs -r -y $DEVICE >&$SILENT_OUT 2>&1
      fi
    done
  }

  load_modules() {
    progress "Loading kernel modules"

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

  load_splash() {
    progress "Loading bootsplash"

    if [ -e /dev/fb0 -a ! "$SPLASH" = "no" ]; then

      # set framebuffer to a default resolution (1024x768-32)
        fbset -g 1024 768 1024 768 32

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

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

  check_disks() {
    progress "Checking disks"

    if [ -x /bin/fsck_hfs ]; then
    # deal with hfs partitions
      hfsdiskprep
    fi
  }

  mount_flash() {
    progress "Mounting flash"

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

  mount_storage() {
    progress "Mounting storage"

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

        mount_part "$disk" "/storage" "rw,noatime"
        mkdir -p /storage/$OVERLAY_DIR
        /bin/busybox 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"

#    if [ -f "/flash/MACH_KERNEL" ]; then
#      IMAGE_KERNEL="MACH_KERNEL"
#    fi

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

      # check md5 sums if .nocheck doesn't exist
      if [ "$MD5_NOCHECK" -eq "0" -a -f "$UPDATE_DIR/${UPDATE_KERNEL}.md5" -a -f "$UPDATE_DIR/${UPDATE_SYSTEM}.md5" ] ; then
        /bin/busybox sed  -i 's#target#/storage/.update#g' "$UPDATE_DIR/${UPDATE_KERNEL}.md5"
        /bin/busybox sed  -i 's#target#/storage/.update#g' "$UPDATE_DIR/${UPDATE_SYSTEM}.md5"

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

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

      # 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
        REBOOT="1"
      else
        /bin/busybox rm "$UPDATE_DIR/$UPDATE_KERNEL"
        /bin/busybox rm "$UPDATE_DIR/$UPDATE_SYSTEM"
        echo "md5 check failed. normal startup in 30s..."
        /bin/busybox sync
        /bin/busybox usleep 30000000
      fi
      /bin/busybox rm "$UPDATE_DIR/${UPDATE_KERNEL}.md5" &>/dev/null
      /bin/busybox rm "$UPDATE_DIR/${UPDATE_SYSTEM}.md5" &>/dev/null
    fi

    if test "$REBOOT" -eq "1"; then
      echo "System reboots now..." && \
      /bin/busybox reboot
    fi
  }

  prepare_sysroot() {
    progress "Preparing system"

    if [ -f "/flash/$IMAGE_SYSTEM" ]; then
    # /flash is filesystem with system image file
      INSTALLED_MEMORY=`cat /proc/meminfo | grep 'MemTotal:' | awk '{print $2}'`
      SYSTEM_TORAM_LIMIT=1024000

      if [ -f "/etc/initramfs.conf" ]; then
        . /etc/initramfs.conf
      fi

      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

      /bin/busybox mount --move /flash /sysroot/flash
    else
    # /flash is actual root filesystem
      /bin/busybox mount --move /flash /sysroot
    fi

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

    [ -f "/sysroot/sbin/init" ] || error "final_check" "Could not find system."
  }

# 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

# 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

# switch to new sysroot and start real init
  exec /bin/busybox switch_root /sysroot /sbin/init

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