##
## Copyright 2014, 2015 Cumulus Networks Inc.
## All rights reserved
##
## PowerPC Installer functions and utilities
##
## This assumes the shell is ash/dash.
##
## Assume we are in an environment that supplies functions like:
##   cl_run
##   log_failure_msg
##   log_success_msg
##   log_begin_msg
##   log_end_msg
##
## These functions support a number of operations on Cumulus image
## files, including:
##
##   verify  -- Verify image integrity and return.
##   extract -- Extract constituent files, no installation performed.
##   info    -- Dump image meta data and file TOC.
##   install -- Extract and install image files.
##   env     -- Extract and install u-boot environment variables.
##
## On error the functions herein return 1 and do not exit.  The
## callers of these functions need to check the status codes and clean
## things up themselves.

[ -r "$script_dir/common/uboot-installer-functions" ] || {
    log_failure_msg "Problem sourcing shell script: $script_dir/uboot-installer-functions"
    exit 1
}
. $script_dir/common/uboot-installer-functions

default_kernel_file="uImage-${arch}.itb"
default_sysroot_file="sysroot.squash.xz"

# Default PowerPC kernel install method
# Install uImage into /dev/mtd-open
install_kernel()
{
    slot=$1
    kimage=$2

    dst="/dev/mtd-open"

    # determine erase block size
    blk_sz=$(awk '{ if ($4 == "\"open\"") { print $3; } }' /proc/mtd)
    blk_sz="0x$blk_sz"
    # convert to decimal
    blk_sz=$(( $blk_sz + 0 ))

    # determine image size
    kimage_sz=$(stat -c '%s' $kimage)

    # convert to decimal
    image_sz=$(( $image_sz + 0 ))
    if [ $kimage_sz -gt $image_sz ] ; then
	log_failure_msg "Kernel image size is too large: $kimage_sz"
	return 1
    fi

    if [ "$dry_run" = "y" ] ; then
	_log_msg "${log_pre}Flash programming $kimage $dst.\n"
	return 0
    fi

    # determine number of erase blocks
    blk_cnt=$(_calc_blks $kimage_sz $blk_sz)

    if [ "$slot" = "1" ] ; then
	mtd_offset="$image1_off"
    else
	mtd_offset="$image2_off"
    fi
    # convert to decimal
    mtd_offset=$(( $mtd_offset + 0 ))

    # erase mtd region
    cl_run flash_erase $dst $mtd_offset $blk_cnt || {
	log_failure_msg "flash_erase $dst $mtd_offset $blk_cnt failed."
	return 1
    }

    # install kernel to mtd
    seek_blks=$(_calc_blks $mtd_offset $blk_sz)
    $DD if=$kimage of=$dst bs=$blk_sz seek=$seek_blks 2> /dev/null || {
	log_failure_msg "$DD if=$kimage of=$dst bs=$blk_sz seek=$seek_blks failed."
	return 1
    }

    # verify copy - copy out and sha1
    new_kernel_sha1=$($DD if=$dst bs=$blk_sz skip=$seek_blks count=$blk_cnt 2> /dev/null | head -c $kimage_sz | $SHA1SUM | awk '{ print $1 }')
    [ $? -eq 0 ] || {
	log_failure_msg "Unable to extract new kernel uImage checksum."
	return 1
    }
    # compare sha1 to original
    org_kernel_sha1=$(cat ${kimage}.sha1 | awk '{ print $1 }')
    if [ "$org_kernel_sha1" != "$new_kernel_sha1" ] ; then
	log_failure_msg "Unable to verify kernel uImage checksum."
	return 1
    fi

    return 0
}

# install sysroot
install_sysroot()
{
    slot=$1

    if [ $root_type = 'blk' ] ; then
        install_sysroot_blk $slot
    elif [ $root_type = 'ubi' ] ; then
        install_sysroot_ubi $slot
    fi

    if [ $? -ne 0 ] ; then
        _log_msg "Failed.\n"
        log_failure_msg "Unable to copy $sysroot"
        # set the slot state to error
        cl_slot_state_set $slot 2
        return 1
    fi
    sync
    log_end_msg
}

# install sysroot into ubi volume
install_sysroot_ubi()
{
    slot=$1

    dst=`ubi_name_to_device sysroot${slot}`
    if [ $? -ne 0 ]; then
        log_failure_msg "ubi volume does not exist: sysroot${slot}"
        return 1
    fi

    if [ "$dry_run" = "y" ] ; then
        _log_msg "${log_pre}Copying sysroot into $dst\n"
        return 0
    fi

    log_begin_msg "Copying sysroot into ubi:sysroot${slot}"

    sysroot_tmp=$(mktemp -d)
    mount -t ubifs ubi:sysroot${slot} $sysroot_tmp
    if [ $? -ne 0 ]; then
        log_failure_msg "unable to mount ubi:sysroot${slot}"
        rmdir $sysroot_tmp
        return 1
    fi

    sysroot_file=$sysroot_tmp/sysroot

    $TAR -xOf $data_tar $sysroot > $sysroot_file
    tar_rv=$?

    umount $sysroot_tmp
    rmdir $sysroot_tmp

    if [ $tar_rv -ne 0 ]; then
        log_failure_msg "failed to untar sysroot"
        return 1
    fi

    return 0
}

# install sysroot into block device using dd
install_sysroot_blk()
{
    slot=$1

    if [ "$slot" = "1" ] ; then
        dst="/dev/$ro_part1"
    else
        dst="/dev/$ro_part2"
    fi

    [ -b "$dst" ] || {
        log_failure_msg "sysroot destination is not a valid block device: $dst"
        return 1
    }

    if [ "$dry_run" = "y" ] ; then
        _log_msg "${log_pre}Copying sysroot into $dst\n"
        return 0
    fi

    log_begin_msg "Copying sysroot into $dst"

    $TAR -xOf $data_tar $sysroot | $DD of=$dst bs=1M 2> /dev/null
    return $?
}

verify_sysroot()
{
    slot=$1
    expected_sha1=$2
    sysroot_size=$3

    if [ $root_type = 'blk' ] ; then
        verify_sysroot_blk $slot $expected_sha1 $sysroot_size
    elif [ $root_type = 'ubi' ] ; then
        verify_sysroot_ubi $slot $expected_sha1 $sysroot_size
    fi

    return $?
}

verify_sysroot_blk()
{
    slot=$1
    expected_sha1=$2
    sysroot_size=$3

    if [ "$slot" = "1" ] ; then
        src="/dev/$ro_part1"
    else
        src="/dev/$ro_part2"
    fi

    [ -b "$src" ] || {
        log_failure_msg "sysroot source is not a valid block device: $src"
        return 1
    }

    if [ "$dry_run" = "y" ] ; then
        # use the actual value
        return 0
    fi

    sha1=$($DD if=$src bs=1M 2> /dev/null | head -c $sysroot_size | \
        $SHA1SUM | awk '{ print $1 }')
    [ $? -eq 0 ] || {
        log_failure_msg "Unabled to verify sysroot checksum."
        # set the slot state to error
        cl_slot_state_set $slot 2
        return 1
    }

    if [ $sha1 != $expected_sha1 ]; then
        log_failure_msg "${log_pre}Expected hash: $expected_sha1."
        log_failure_msg "${log_pre}Computed hash: $sha1."
        return 1
    fi

    return 0
}

verify_sysroot_ubi()
{
    slot=$1
    expected_sha1=$2
    sysroot_size=$3

    if [ "$dry_run" = "y" ] ; then
        # use the actual value
        return 0
    fi

    sysroot_tmp=$(mktemp -d)
    mount -t ubifs ubi:sysroot${slot} $sysroot_tmp
    if [ $? -ne 0 ]; then
        log_failure_msg "unable to mount ubi:sysroot${slot}"
        rmdir $sysroot_tmp
        return 1
    fi

    sysroot_file=$sysroot_tmp/sysroot

    sha1="$($SHA1SUM $sysroot_file | awk '{ print $1 }')"
    sha1_rv=$?

    umount $sysroot_tmp
    rmdir $sysroot_tmp

    [ $sha1_rv -eq 0 ] || {
        log_failure_msg "Unabled to verify sysroot checksum."
        # set the slot state to error
        cl_slot_state_set $slot 2
        return 1
    }

    if [ $sha1 != $expected_sha1 ]; then
        log_failure_msg "${log_pre}Expected hash: $expected_sha1."
        log_failure_msg "${log_pre}Computed hash: $sha1."
        return 1
    fi

    return 0
}

part_ubi()
{
    _log_msg "\n"

    ubiformat /dev/$mtd_dev -y
    if [ $? -ne 0 ] ; then
	log_failure_msg "${log_pre}unable to ubiformat $mtd_dev"
	return 1
    fi

    mtd_num=`ls -l /dev/$mtd_dev | tail -c2`
    ubiattach -m $mtd_num /dev/ubi_ctrl
    if [ $? -ne 0 ] ; then
	log_failure_msg "${log_pre}unable to ubiattach $mtd_dev"
	return 1
    fi

    # Note: there is a bug in ubimkvol that reqires no space in "-tstatic"
    ubimkvol /dev/ubi0 -N sysroot1 -s $(($sysroot1_size*1024*1024))
    ubimkvol /dev/ubi0 -N sysroot2 -s $(($sysroot2_size*1024*1024))
    ubimkvol /dev/ubi0 -N persist -s $(($persist_size*1024*1024))
    if [ $overlay_rw_size = 'fill' ]; then
	ubimkvol /dev/ubi0 -N overlay_rw -s \
	       $((`cat /sys/class/ubi/ubi0/avail_eraseblocks` * \
		  `cat /sys/class/ubi/ubi0/eraseblock_size`))
    else
	ubimkvol /dev/ubi0 -N overlay_rw -s $(($overlay_rw_size*1024*1024))
    fi

    persist_dev=`ubi_name_to_device persist`
    rw_rootdev=`ubi_name_to_device overlay_rw`
}

# This function runs in ONIE context.
format_disk()
{
    [ "$skip_disk_format" = "y" ] && return 0

    log_pre=
    if [ "$dry_run" = "y" ] ; then
	log_pre="Dry run: "
    fi

    if [ $root_type = 'blk' -a ! -f "$platform_part" ]; then
	log_failure_msg "$plat: Platform partition file is missing"
	return 1
    fi

    if [ "$dry_run" != "y" ] ; then
	if [ $root_type = 'blk' ]; then
	    log_begin_msg "${log_pre}Partitioning /dev/$blk_dev"
            # First write out blank partition table -- CM-4117
            # printf "o\nw\n" | fdisk -u /dev/$blk_dev >& /dev/null &&
	    # fdisk -u /dev/$blk_dev < $platform_part >& /dev/null
	    log_begin_msg "This script does not make real partitions,"
	    log_begin_msg "see control.tar.xz/scripts/arch-installer-functions"
	else
	    log_begin_msg "${log_pre}Partitioning /dev/$mtd_dev"
	    part_ubi /dev/$mtd_dev
	fi
    fi
    if [ "$?" != "0" ] ; then
	log_failure_msg "${log_pre}Partitioning failed."
	return 1
    fi
    log_end_msg

    if [ $root_type = 'ubi' ]; then
	# create a filesystem on the sysroot volumes, no compression
	log_begin_msg "${log_pre}Formatting sysroots"
	if [ "$dry_run" != "y" ] ; then
	    mkfs.ubifs -x none `ubi_name_to_device sysroot1` &&
	    mkfs.ubifs -x none `ubi_name_to_device sysroot2`
	fi
	if [ "$?" != "0" ] ; then
	    log_failure_msg "${log_pre}mkfs.ubifs $sysroot_dev failed."
	    return 1
	fi
	log_end_msg
    fi

    log_begin_msg "${log_pre}Formatting $persist_dev"
    if [ "$dry_run" != "y" ] ; then
	mkfs.$persist_type $persist_dev >& /dev/null
    fi
    if [ "$?" != "0" ] ; then
	log_failure_msg "${log_pre}mkfs.$persist_type $persist_dev failed."
	return 1
    fi
    log_end_msg

    log_begin_msg "${log_pre}Formatting $rw_rootdev"
    if [ "$dry_run" != "y" ] ; then
	mkfs.$rw_type $rw_rootdev >& /dev/null
    fi
    if [ "$?" != "0" ] ; then
	log_failure_msg "${log_pre}mkfs.$rw_type $rw_rootdev failed."
	return 1
    fi
    log_end_msg
}

# This function runs in ONIE context.
# Sets variables used by generic save_info() routine.
arch_set_savedir()
{
    # Use the common rw partition to store the provisioning information
    savedir_type="$rw_type"
    savedir_opts="$rw_opts"
    savedir_dev="$rw_rootdev"
    # Save the image used during provisioning
    savedir_img=yes
}

# This function is called after installing an image into a slot.
# For powerpc this is a NO-OP.
arch_post_slot_install()
{
    true
}

##
# Local Variables:
# mode: shell-script
# eval: (sh-set-shell "/bin/sh" t nil)
# End:
