#!/bin/bash
#
# This script is designed to expand an image bank by taking space away
# from the log partition.  It needs to be run prior to mounting the
# partitions that are to be resized.  The easiest way to do this is to
# boot up a system using the nosaos flag.  This script may also work on
# an NFS mounted system, but that has not been tested.
#
# This is intended to be a designer tool to allow installation of images
# that are larger than the default bank size (for prototyping purposes).
#

# globals
debug=0       # enabled by --debug or --dry-run
dry_run=0     # enabled by --dry-run
targ_size=160 # override with --size
standby=0     # enabled by --standby

source /ciena/scripts/utils.sh

EXIT_SUCCESS=0
EXIT_ERROR=1
EXIT_INVALID_ARGS=2
EXIT_NOT_ROOT=3
EXIT_COMMAND_FAILED=4

# -----------------------------------------------------------------------------
print_usage_and_exit() {
    echo
    echo "Usage: $this_script [options] <A|B>"
    echo
    echo -e "\tThis script will expand the target image partition."
    echo
    echo "Options:"
    echo -e "\t--debug    displays debug information while running"
    echo -e "\t--dry-run  shows commands that will be run (but does nothing)"
    echo -e "\t--standby  expands the standby bank"
    echo -e "\t--size <x> provides an alternate target size in MB (default=$targ_size)"
    echo -e "\t--help     shows this message"
    echo
    exit $EXIT_INVALID_ARGS
}

# -----------------------------------------------------------------------------
log_msg()
{
    local _level="$1"
    local _msg="$2"

    printf "%-5s: %s\n" "$_level" "$_msg"
}

# -----------------------------------------------------------------------------
error_exit()
{
    local _msg="$1"
    local _exit_code="$2"

    if [ -z "$_exit_code" ] ; then
        _exit_code="$EXIT_ERROR"
    fi

    log_msg ERROR "$_msg"

    exit $_exit_code
}

# -----------------------------------------------------------------------------
command_wrapper() {
    local comment=$1; shift
    local command_line="$*"

    log_msg INFO "$comment"

    if [ "$debug" -eq "1" ] ; then
        echo "$command_line"
        echo
    fi

    if [ "$dry_run" -eq "1" ] ; then
        return 0
    fi

    $command_line
}

# -----------------------------------------------------------------------------
exit_on_error() {
    local comment=$1

    command_wrapper "$@"

    if [ "$?" -ne "0" ] ; then
        log_msg ERROR "$comment failed"

        # recovery code would go here, if there was any

        exit $EXIT_COMMAND_FAILED
    fi
}

# -----------------------------------------------------------------------------
ubifs_is_mounted()
{
    mount | grep -q ubi0:$1
}

# -----------------------------------------------------------------------------
ubifs_mount_point()
{
    grep ubi0:$1 /etc/fstab | awk '{ print $2 }'
}

# -----------------------------------------------------------------------------
resize_ubifs_partition()
{
    local _name="$1"      # ubifs volume name
    local _leb_targ="$2"  # new leb count
    local _ro_mount="$3"  # mount point is read-only

    local _mount_point=$(ubifs_mount_point $_name)

   # check that partition is not in use
    if ubifs_is_mounted $_name ; then
        command_wrapper "unmount $_mount_point" umount $_mount_point
        if [ "$?" -ne "0" ] ; then
            log_msg INFO "current users via lsof:"
            lsof $_mount_point
            error_exit "could not unmount $_mount_point"
        fi
    fi

    # create a tmpfs to hold volume contents and copy the guts over

    exit_on_error "mount $_mount_point" mount $_mount_point

    tmp_size=$(du -s $_mount_point | cut -f1)   # grab the size
    tmp_size=$((tmp_size+100))                  # add some extra

    exit_on_error "mkdir /tmp/space" mkdir -p /tmp/space

    exit_on_error "mount /tmp/space ${tmp_size}K" \
        mount -t tmpfs tmpfs /tmp/space -o size=${tmp_size}K

    # need to kick watchdog here for 5150
    if [ "$standby_bank" -eq "1" ] ; then
        kick_watchdog
    fi

    exit_on_error "copy $_mount_point to /tmp/space" \
        cp -a ${_mount_point}/. /tmp/space

    # resize and format ubifs volume
    
    exit_on_error "unmount ${_mount_point}" umount ${_mount_point}

    exit_on_error "resize ${_name} volume" \
        ubirsvol /dev/ubi0 -N ${_name} --lebs=${_leb_targ}

    local vol_id=$(ubinfo /dev/ubi0 --name ${_name} \
        | awk '/^Volume ID:/ {print $3}')

    exit_on_error "format ubifs for ${_name} volume" \
        mkfs.ubifs --yes /dev/ubi0_${vol_id}

    exit_on_error "mount ${_mount_point}" mount ${_mount_point} -o rw

    # need to kick watchdog here for 5150
    if [ "$standby_bank" -eq "1" ] ; then
        kick_watchdog
    fi

    exit_on_error "copy to ${_mount_point}" cp -a /tmp/space/. ${_mount_point}


    if [ "$standby_bank" -eq "1" ] ; then
        exit_on_error "unmount ${_mount_point}" umount ${_mount_point}
    else
        if [ -n ${_ro_mount} ] ; then
            exit_on_error "mount ${_mount_point} ro" \
                mount ${_mount_point} -o remount,ro
        fi
    fi 

    exit_on_error "unmount /tmp/space" umount /tmp/space

    exit_on_error "remove /tmp/space" rm -r /tmp/space
    
    # need to kick watchdog here for 5150
    if [ "$standby_bank" -eq "1" ] ; then
        kick_watchdog
    fi

}

# -----------------------------------------------------------------------------
shrink_log_partition()
{
    local _leb_steal="$1"  # how many blocks to take from the log partition
    local _leb_thresh=600  # how many blocks to reserve for the log partition
    local _leb_curr=$(ubinfo /dev/ubi0 -N logs | awk '/^Size:/ {print $2}')
    local _leb_targ=$((_leb_curr - _leb_steal))

    log_msg INFO "reducing log partition ($_leb_curr) by $_leb_steal blocks"

    if [ "$_leb_targ" -lt "$_leb_thresh" ] ; then
        error_exit "not enough free blocks in log partition ($_leb_targ < $_leb_thresh)"
    fi

    # If the partition isn't mounted, mount it so that we can clear core files.
    if ! ubifs_is_mounted logs ; then
        exit_on_error "mount /mnt/log" mount /mnt/log
    fi

    exit_on_error "clear /mnt/log/corefiles" \
        rm -rf /mnt/log/corefiles/*

    resize_ubifs_partition "logs" "${_leb_targ}"
}

# -----------------------------------------------------------------------------
mbytes_to_leb_count()
{
    local _mbytes=$1
    local _leb_size=$(ubinfo -a | awk '/Logical eraseblock size:/ { print $4}')
    local _leb_count=0

    if [[ $_mbytes =~ [0-9]+$ ]] ; then
        _leb_count=$((_mbytes * 1024 * 1024 / _leb_size))
        _leb_count=$((_leb_count + 1))
    fi

    echo $_leb_count
} 

# -----------------------------------------------------------------------------
# main script
#

# Take note of our name as invoked
this_script=$(basename $0)

# handle options
while :; do
    case "$1" in
        "--help"|"-h"|"-?"|"help") print_usage_and_exit ;;
        "--debug") debug=1 ; shift ;;
        "--dry-run") debug=1 ; dry_run=1 ; shift ;;
        "--size") shift ; targ_size=$1 ; shift ;;
        "--standby") standby_bank=1 ; shift ;;
        *) break ;;
    esac
done

targ_leb_count=$(mbytes_to_leb_count $targ_size)

if [ "$targ_leb_count" -eq "0" ] ; then
    error_exit "invalid target size  \"$targ_size\"" $EXIT_INVALID_ARGS
fi

# get positional arguments
if [ "$standby_bank" -eq "1" ] ; then
    /ciena/scripts/software_bank > /dev/null
    case "$?" in
        "1") target_bank='B' ;;
        "2") target_bank='A' ;;
        *) break ;;
    esac
else
    target_bank=$1
fi

if [ "$#" -eq "0" ] && [ "$standby_bank" -eq "0" ] ; then
    log_msg ERROR "missing target image partition"
    print_usage_and_exit
fi

case ${target_bank} in
    'A' | 'B' ) ;;
    *) error_exit "invalid target bank \"$target_bank\"" $EXIT_INVALID_ARGS
esac

# check that we can proceed:
# - product family must be caliondo
# - must not resize the running rootfs
# - must be running as root

family=$(cat /family)
model=$(get_board_name_pretty)

if [ "$family" != "caliondo" ] ; then
    error_exit "can not run on $family $model (only supported on caliondo)"
fi

rootfs_bank=$(kernel_arg root)
rootfs_bank=${rootfs_bank#ubi0:app}

if [ "$rootfs_bank" == "$target_bank" ] ; then
    error_exit "can not resize running rootfs" $EXIT_INVALID_ARGS
fi

if [ "$(id -u)" -ne "0" ] ; then
    error_exit "must be running as root" $EXIT_NOT_ROOT
fi

# check size of target bank
curr_leb_count=$(ubinfo --devn=0 --name=app${target_bank} \
    | awk '/Size/ {print $2}')

if [ "$curr_leb_count" -ge "$targ_leb_count" ] ; then
    log_msg INFO "size=$curr_leb_count, targ=$targ_leb_count, no increase required"
    exit $EXIT_SUCCESS
fi

# resize logic starts here

log_msg INFO "attempting to resize app${target_bank} to ${targ_leb_count} blocks ($targ_size MB)"

free_leb_count=$(ubinfo -a \
    | awk '/Amount of available logical eraseblocks:/ { print $6}')

extr_leb_count=$((targ_leb_count - curr_leb_count - free_leb_count))

# If there are already enough unused blocks, then just use them.  Otherwise
# steal blocks from the log partition to be used in the app partition.

if [ "$extr_leb_count" -gt "0" ] ; then
    shrink_log_partition $extr_leb_count
else
    log_msg INFO "only free blocks will be used for increase"
fi

resize_ubifs_partition "app${target_bank}" "${targ_leb_count}" "yes"

log_msg INFO "successfully expanded app${target_bank}"

exit $EXIT_SUCCESS
