#
# Copyright 2014, 2015 Cumulus Networks Inc.
# All rights reserved
#
# Common installer functions for partitioning disks and installing the
# kernel and initramfs.  At the moment this includes common functions
# for:
#
# - GPT partitioning
# - LVM allocations
# - UBI/UBIFS partitioning

# Supress annoying, but harmless warning messages from the LVM tools.
# We use flock() on a file descriptor to hold a lock during install.
# LVM is built to warn and close all open FDs except stdin, stdout and
# stderr.
export LVM_SUPPRESS_FD_WARNINGS=yes

default_kernel_file="vmlinuz-initrd.tar.xz"
volume_group="CUMULUS"

# Extract the file list mapping table and source the variable names
# within.
$TAR -xf $data_tar file.list || {
    log_failure_msg "Unable to extract file list mapping table: file.list"
    return 1
}
. "$(pwd)/file.list" || {
    log_failure_msg "Unable to source file list mapping table: $(pwd)/file.list"
    return 1
}
# Check for expected variables
for v in img_vmlinuz img_initrd img_sysroot ; do
    eval tmp_var='$'${v}
    [ -n "tmp_var" ]  || {
        log_failure_msg "file.list: $v variable is empty"
        return 1
    }
done

# Source the file list mapping table and return the name of the
# sysroot archive.
get_sysroot_file()
{
    echo -n "$img_sysroot"
}

# Mount a partition with error checking
# arg $1 - file system type
# arg $2 - device
# arg $3 - mount point
# arg $4 - mount options [optional]
mount_part()
{
    local fs_type="$1"
    local mount_dev="$2"
    local mount_dir="$3"
    local opts="$4"

    mount -t $fs_type $opts $mount_dev $mount_dir || {
        log_failure_msg "Problems mounting $mount_dev on $mount_dir"
        return 1
    }
}

boot_mnt=
boot_device=
# Mount the /boot partition on a temp directory and set the $boot_mnt
# variable.
mount_boot()
{
    boot_mnt=$(mktemp -d)

    if [ $root_type = 'tar' ]; then
        boot_device="LABEL=$boot_label"
    elif [ $root_type = 'ubi-tar' ]; then
        boot_device="ubi:boot"
    else
        log_failure_msg "${log_pre}Invalid device type: $root_type"
        return 1
    fi

    mount_part $boot_type "$boot_device" $boot_mnt || {
        rmdir $boot_mnt
        return 1
    }
}

# Un-mount $boot_mnt and clean up
umount_boot()
{
    umount $boot_mnt > /dev/null 2>&1
    rmdir $boot_mnt > /dev/null 2>&1
}

# Default kernel install method
# Install kernel+initramfs into correct partition
install_kernel()
{
    local slot=$1
    local kimage=$(realpath $2)

    if [ "$dry_run" = "y" ] ; then
        _log_msg "${log_pre}Installing $kimage to $boot_label.\n"
        return 0
    fi

    log_begin_msg "${log_pre}Copying kernel into $boot_label partition"
    local kernel_tmp=$(mktemp -d)
    ( cd $kernel_tmp && $XZ -d -c $kimage | $TAR xf - ) || {
        echo "Error: Unable to extract $kimage"
        rm -rf $kernel_tmp
        return 1
    }
    log_end_msg

    log_begin_msg "${log_pre}Verifying kernel copy"
    local cp_ok=yes
    for f in $img_vmlinuz $img_initrd ; do
        cat $kernel_tmp/$f | $SHA1SUM -s -c $kernel_tmp/${f}.sha1 || cp_ok=no
    done

    mount_boot || return 1

    if [ "$cp_ok" = "yes" ] ; then
        _log_msg " OK.\n"
        # Since all the kernels and initrd go into the same directory
        # make sure each file name is unique by appending the slot.
        #
        # In addition, remove any pre-existing kernel+initramfs for
        # the target slot.
        rm -f $boot_mnt/${kernel_prefix}*-${slot_prefix}$slot
        local kernel="${kernel_prefix}${img_vmlinuz}-${slot_prefix}$slot"
        local initrd="${kernel_prefix}${img_initrd}-${slot_prefix}$slot"
        cp $kernel_tmp/$img_vmlinuz $boot_mnt/$kernel
        cp $kernel_tmp/$img_initrd $boot_mnt/$initrd
    else
        _log_msg " Failed.\n"
        umount_boot
        log_failure_msg "${log_pre}Unable to verify kernel copy to slot ${slot}."
        return 1
    fi

    rm -rf $kernel_tmp

    umount_boot

    return 0
}

# Pre-handler for installing the sysroot on systems with block
# devices.
#
# $1 - slot
# $2 - sysroot_mnt  -- sysroot mount point
# $3 - sysroot_size
#
install_sysroot_blk_pre()
{
    local slot=$1
    local sysroot_mnt=$2
    local set_sysroot_size=$3

    [ -z "${set_sysroot_size}" ] && {
        set_sysroot_size="$(lvdisplay -c ${volume_group}/${sysroot_label}$slot | cut -d ':' -f 8)"
    }

    # Initialize a new slot
    make_lvm_lv $volume_group "${sysroot_label}$slot" "$set_sysroot_size" $sysroot_type || {
        log_failure_msg "${log_pre}Problems creating/formatting partition ${sysroot_label}$slot"
        return 1
    }

    # Mount destination slot sysroot
    mount_part $sysroot_type LABEL="${sysroot_label}$slot" $sysroot_mnt || {
        log_failure_msg "${log_pre}Problems mounting ${sysroot_label}$slot"
        return 1
    }
}

# Post-handler for installing the sysroot on systems with block
# devices.
#
# $1 - slot
# $2 - sysroot_mnt  -- sysroot mount point
#
install_sysroot_blk_post()
{
    local slot=$1
    local sysroot_mnt=$2

    # Set up /etc/fstab
    local root_uuid="$(cl_label_to_uuid ${sysroot_label}$slot)"
    [ -n "$root_uuid" ] || {
        log_failure_msg "Problems finding UUID for ${sysroot_label}$slot partition"
        return 1
    }
    local boot_uuid="$(cl_label_to_uuid ${boot_label})"
    [ -n "$boot_uuid" ] || {
        log_failure_msg "Problems finding UUID for ${boot_label} partition"
        return 1
    }
    local persist_uuid="$(cl_label_to_uuid ${persist_label})"
    [ -n "$persist_uuid" ] || {
        log_failure_msg "Problems finding UUID for ${persist_label} partition"
        return 1
    }
    cat <<EOF >> $sysroot_mnt/etc/fstab
UUID=${root_uuid}	/		$sysroot_type	$sysroot_opts	0	1
UUID=${boot_uuid}	/boot		$boot_type	$boot_opts	0	2
UUID=${persist_uuid}	/mnt/persist	$persist_type	$persist_opts	0	2
EOF

}

# Pre-handler for installing the sysroot on systems with ubifs
# devices.
#
# $1 - slot
# $2 - sysroot_mnt  -- sysroot mount point
#
install_sysroot_ubi_pre()
{
    local slot=$1
    local sysroot_mnt=$2

    # Mount destination slot sysroot
    mount_part $sysroot_type "ubi:sysroot${slot}" $sysroot_mnt || {
        log_failure_msg "${log_pre}Problems mounting sysroot${slot}"
        return 1
    }

}

# Post-handler for installing the sysroot on systems with ubifs
# devices.
#
# $1 - slot
# $2 - sysroot_mnt  -- sysroot mount point
#
install_sysroot_ubi_post()
{
    local slot=$1
    local sysroot_mnt=$2

    # Set up /etc/fstab
    #
    # Note: The fs_passno field is set to "0", indicating no fsck is
    # required for ubifs.
    cat <<EOF >> $sysroot_mnt/etc/fstab
ubi:sysroot${slot}	/		$sysroot_type	$sysroot_opts	0	0
ubi:boot		/boot		$boot_type	$boot_opts	0	0
ubi:persist		/mnt/persist	$persist_type	$persist_opts	0	0
EOF
}

# install sysroot into block device using tar
install_sysroot()
{
    local slot=$1
    local set_sysroot_size=$2

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

    log_info_msg "Copying sysroot into slot $slot"

    # Create mount point for the sysroot
    local sysroot_mnt=$(mktemp -d)

    # Initial preparation for installing the sysroot
    if [ $root_type = 'tar' ]; then
        install_sysroot_blk_pre $slot $sysroot_mnt $set_sysroot_size
    elif [ $root_type = 'ubi-tar' ]; then
        install_sysroot_ubi_pre $slot $sysroot_mnt
    else
        log_failure_msg "${log_pre}Invalid device type: $root_type"
        return 1
    fi
    [ "$?" != "0" ] && {
        umount $sysroot_mnt > /dev/null 2>&1
        rmdir $sysroot_mnt > /dev/null 2>&1
        return 1
    }

    # Common sysroot install steps.  Copy tar contents into
    # destination sysroot.

    # Note: tar requires the compression type when reading from stdin.
    $TAR -xOf $data_tar $sysroot | $TAR -C $sysroot_mnt -xzf - || {
        log_failure_msg "Problems untaring $sysroot into $sysroot_mnt."
        umount $sysroot_mnt
        rmdir $sysroot_mnt
        return 1
    }
    sync; sync

    mkdir -p $sysroot_mnt/mnt/persist

    # Final steps when installing the sysroot
    if [ $root_type = 'tar' ]; then
        install_sysroot_blk_post $slot $sysroot_mnt
    elif [ $root_type = 'ubi-tar' ]; then
        install_sysroot_ubi_post $slot $sysroot_mnt
    else
        log_failure_msg "${log_pre}Invalid device type: $root_type"
        return 1
    fi
    [ "$?" != "0" ] && {
        umount $sysroot_mnt > /dev/null 2>&1
        rmdir $sysroot_mnt > /dev/null 2>&1
        return 1
    }

    cp -a $INIT_CONF/running $INIT_CONF/arch.conf $sysroot_mnt/etc/cumulus/init

    umount $sysroot_mnt
    rmdir $sysroot_mnt

    log_end_msg

}

verify_sysroot()
{
    # This is a NO-OP for the tar sysroot method
    true
}

. $platform_part

#
# Create a partition and optionally create a file system on it.
#
make_format_part()
{
    local blk_dev="$1"
    local part_name="$2"
    local part_num="$3"
    local part_size="$4"
    local fs_type="$5"

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

    local blk_suffix=
    case ${blk_dev} in
        mmcblk*)
            blk_suffix=p
            ;;
    esac

    # For now we are assuming GPT partitions...
    log_begin_msg "${log_pre}Creating $part_name partition on /dev/${blk_dev}${part_num}"
    if [ "$dry_run" != "y" ] ; then
        sgdisk --new=${part_num}::+${part_size}M \
            --change-name=${part_num}:"$part_name" /dev/$blk_dev > /dev/null 2>&1 || {
            log_failure_msg "${log_pre}Unable to create partition $part_name, $part_num on /dev/$blk_dev"
            return 1
        }
        # Refresh kernel's view of the partition table
        partprobe
    fi

    if [ -n "$fs_type" ] ; then
        if [ "$dry_run" != "y" ] ; then
            mkfs.${fs_type} -L "$part_name" /dev/${blk_dev}${blk_suffix}${cur_part} > /dev/null 2>&1 || {
                log_failure_msg "${log_pre}Unable to make $fs_type file system on /dev/${blk_dev}${blk_suffix}${cur_part}"
                return 1
            }
        fi
    fi

    log_end_msg
}

# This function runs in ONIE context.
#
# Setup the volume group for using LVM.  This entails:
#
# 1. create a physical disk partition
# 2. create a LVM "Physical Volume (PV)" using the partition from step 1.
# 3. create a LVM "Volume Group (VG)" using the PV from step 2.
#
make_lvm_vg()
{
    local blk_dev="$1"
    local part_name="$2"
    local part_num="$3"
    local part_size="$4"

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

    local blk_suffix=
    case ${blk_dev} in
        mmcblk*)
            blk_suffix=p
            ;;
    esac

    # For now we are assuming GPT partitions...
    log_begin_msg "${log_pre}Creating $part_name partition on /dev/${blk_dev}${part_num}"
    if [ "$dry_run" != "y" ] ; then
        # Set system partition (bit 0) attribute.
        local attr_bitmask="0x1"
        # Use Linux LVM UUID
        local gpt_uuid="8e00"
        local tmp_log=$(mktemp)
        sgdisk --new=${part_num}::${part_size} \
            --typecode=${part_num}:$gpt_uuid \
            --attributes=${part_num}:=:$attr_bitmask \
            --change-name=${part_num}:"$part_name" /dev/$blk_dev > $tmp_log 2>&1 || {
            log_failure_msg "${log_pre}Unable to create partition $part_name, $part_num on /dev/$blk_dev"
            cat $tmp_log
            rm -f $tmp_log
            return 1
        }
        # Refresh kernel's view of the partition table
        partprobe

        # Create LVM physical volume
        local vg_dev="/dev/${blk_dev}${blk_suffix}$part_num"
        pvremove -ff -y $vg_dev > /dev/null 2>&1
        pvcreate -v $vg_dev > $tmp_log 2>&1 || {
            log_failure_msg "${log_pre}Unable to create LVM Physical Volume on $vg_dev"
            cat $tmp_log
            rm -f $tmp_log
            return 1
        }

        # Create LVM volume group
        vgcreate -v "$part_name" $vg_dev > $tmp_log 2>&1 || {
            log_failure_msg "${log_pre}Unable to create LVM Volume Group on /dev/$blk_dev"
            cat $tmp_log
            rm -f $tmp_log
            return 1
        }
        rm -f $tmp_log
    fi

    log_end_msg
}

# This function runs in ONIE context.
#
# Creates a logical volume in the CUMULUS VG.
# Optionally creates a file system on the volume.
#
# part_size is expressed in units of megabytes.
#
make_lvm_lv()
{
    local vg="$1"
    local lv_name="$2"
    local lv_size="$3"
    local fs_type="$4"

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

    log_begin_msg "${log_pre}Creating logical volume $lv_name on volume group $vg"
    if [ "$dry_run" != "y" ] ; then
        # Rare - potentially the logical volume is already mounted
        # from a previous install attempt that was killed
        # ungracefully.  Here, unconditionally try to umount the
        # logical volume before calling lvremove on the logical
        # volume.
        umount "/dev/mapper/${vg}-${lv_name}" > /dev/null 2>&1
        lvremove -f "${vg}/${lv_name}" > /dev/null 2>&1
        local tmp_log=$(mktemp)
        lvcreate -v "-l${lv_size}" "$vg" -n "$lv_name" > $tmp_log 2>&1 || {
            log_failure_msg "${log_pre}Unable to create LVM Logical Volume $lv_name in volume group $vg"
            cat $tmp_log
            rm -f $tmp_log
            return 1
        }
        if [ -n "$fs_type" ] ; then
            mkfs.${fs_type} -L "$lv_name" /dev/${vg}/${lv_name} > /dev/null 2>&1 || {
                log_failure_msg "${log_pre}Unable to make $fs_type file system on /dev/${vg}/${lv_name}"
                return 1
            }
        fi
    fi

    log_end_msg
}

# When deleting partitions this function determines whether or not to
# keep the specified GPT partition.
#
# arg $1 - block device without the "/dev"
# arg $2 - the partition number
#
# This function runs in ONIE context.
#
# XXX - For now we are assuming GPT partitions...
# XXX - this is somewhat draconian.
#
# Returns 0 to delete the partition.
should_delete_partition()
{
    local name="$(sgdisk -i $2 /dev/$1 | grep 'Partition name:')"
    name=${name#*"'"}
    name=${name%"'"*}
    case "$name" in
        GRUB-BOOT|ONIE-BOOT)
            # echo "skipping ONIE part"
            return 1
            ;;
        *-DIAG)
            # check system attributes
            local attr=$(sgdisk -i $2 /dev/$1 | grep 'Attribute flags')
            attr=${attr##*: }
            if [ "$attr" = "0000000000000001" ] ; then
                # system attribute is set, skip this part
                # echo "skipping DIAG part"
                return 1
            fi
            ;;
        *)
            return 0
            ;;
    esac
    return 0
}

# Partition and format routine for systems that use a block device for
# mass storage.  This function runs in ONIE context.
#
# $1 -- check_gpt_init - yes/no whether not to check if GPT label is
#       valid.
boot_partition=
format_disk_blk()
{

    local check_gpt_init=$1

    # By default, for a GRUB/ONIE based system, the lowest possible
    # disk partition number to use for installing is 3.  In this case
    # part1 is GRUB and part2 is ONIE.  It could be larger if a DIAG
    # is present.  This variable is used as a safety check.
    local lowest_part_num=3

    discover_blk_dev || {
        log_failure_msg "${log_pre}Unable to determine block device for installation"
        return 1
    }

    local blk_suffix=
    case ${blk_dev} in
        mmcblk*)
            blk_suffix=p
            ;;
    esac

    # Remove any old partitions
    # Create LVM PV, VG and LVs
    # Format partitions

    if [ "$dry_run" != "y" ] ; then
        # First unmount any active logical volumes and delete the CUMULUS
        # volume group.
        umount /dev/mapper/${volume_group}-* > /dev/null 2>&1
        lvremove -f "$volume_group" > /dev/null 2>&1
        vgremove -f "$volume_group" > /dev/null 2>&1

        if [ "$check_gpt_init" = "yes" ] ; then

            # If the lowest possible partition number in this case is
            # 1 if the GPT label needs initialization.  This number is
            # used as a safety check later on.
            lowest_part_num=1

            # Check if disk has a valid GPT label.
            #
            # This is only needed by ARM platforms at the moment.  For
            # ARM platforms the disk does not necessarily come
            # preformatted from the factory with a GPT label, so we
            # need to check.
            #
            # Look for two cases:
            #
            # 1. invalid GPT disk label
            #
            # 2. absence of a file system with a label of the form *-DIAG
            #
            # If either is true zap the entire disk and reformat with GPT.

            local init_gpt=no

            # valid GPT label ??
            sgdisk -p /dev/$blk_dev > /dev/null 2>&1 || init_gpt=yes

            # is a *-DIAG partition present ??
            blkid | grep -q -- '-DIAG' || init_gpt=yes

            if [ "$init_gpt" = "yes" ] ; then
                # ingore the first --zap-all command as it sometimes
                # fails, even though it does its job.
                sgdisk --zap-all /dev/$blk_dev > /dev/null 2>&1
                local sgdisk_log=$(mktemp)
                sgdisk --zap-all /dev/$blk_dev > $sgdisk_log 2>&1 || {
                    log_failure_msg "Problems creating GPT disk label on: /dev/$blk_dev"
                    cat $sgdisk_log && rm -f $sgdisk_log
                    return 1
                }
                sgdisk --clear /dev/$blk_dev > $sgdisk_log 2>&1 || {
                    log_failure_msg "Problems creating GPT disk label on: /dev/$blk_dev"
                    cat $sgdisk_log && rm -f $sgdisk_log
                    return 1
                }
                rm -f $sgdisk_log
                partprobe
            fi
        fi
    fi

    # Check for any pre-existing partitions, like a OS re-install or a
    # DIAG partition from the hardware vendor.
    if [ -e "/sys/block/$blk_dev/${blk_dev}${blk_suffix}1" ] ; then
        # Wipe out and delete all partitions, except for important ones,
        # like ONIE and possibly a DIAG.
        ls -d /sys/block/$blk_dev/${blk_dev}* | sed -e "s/^.*$blk_dev//" | while read part ; do
            local part_id=$part
            case ${blk_dev} in
                mmcblk*)
                    part_id=${part##p}
                    ;;
            esac

            if should_delete_partition $blk_dev $part_id ; then
                if [ "$dry_run" != "y" ] ; then
                    umount /dev/${blk_dev}$part > /dev/null 2>&1
                    pvremove -ff -y /dev/${blk_dev}$part > /dev/null 2>&1
                    sgdisk -d $part_id /dev/$blk_dev > /dev/null 2>&1 || {
                        log_failure_msg "${log_pre}Problems removing partition $part on /dev/$blk_dev"
                        return 1
                    }
                    partprobe
                fi
            fi
        done
    fi

    # Find next available partition
    local cur_part="$(grep $blk_dev /proc/partitions | wc -l)"
    case ${blk_dev} in
        mmcblk*)
            # mmc devices have 3 extra partitions we do not want to
            # count.  These devices are always present:
            #
            # 179       24        128 mmcblk0rpmb
            # 179       16      16384 mmcblk0boot1
            # 179        8      16384 mmcblk0boot0
            cur_part=$(( $cur_part - 3 ))
            ;;
    esac
    if [ $cur_part -lt $lowest_part_num ] ; then
        log_failure_msg "${log_pre}Starting partition number $cur_part is too small."
        log_failure_msg "${log_pre}Expect the starting partition to be at least $lowest_part_num."
        return 1
    fi

    # Create boot partition -- boot partition cannot be on a LVM
    make_format_part $blk_dev "$boot_label" $cur_part "$boot_size" $boot_type || {
        log_failure_msg "${log_pre}Problems creating/formatting partition $boot_label"
        return 1
    }
    boot_partition="${cur_part}"
    cur_part=$(( $cur_part + 1 ))

    # Create partition for LVM - use all remaining disk space
    make_lvm_vg $blk_dev $volume_group "$cur_part" "" || {
        log_failure_msg "${log_pre}Problems creating/formatting partition $part_name"
        return 1
    }

    # Create persist volume
    local lv_name="$persist_label"
    make_lvm_lv $volume_group "$lv_name" "$persist_size" $persist_type || {
        log_failure_msg "${log_pre}Problems creating/formatting partition $lv_name"
        return 1
    }
}

# Helper function for formatting UBIFS volumes
# $1 - name of UBI partition
# $2 - extra mkfs.ubifs options
make_ubifs()
{
    local name="$1"
    local opts="$2"
    log_begin_msg "${log_pre}Formatting $name volume"
    if [ "$dry_run" != "y" ] ; then
        # Create the filesystem without compression
        mkfs.ubifs $opts "$(ubi_name_to_device $name)" > /dev/null 2>&1 || {
            log_failure_msg "${log_pre}mkfs.ubifs failed: $name"
            return 1
        }
    fi
    log_end_msg
}

# Partition and format routine for systems that use a UBI device for
# mass storage.  This function runs in ONIE context.
format_disk_ubi()
{

    log_begin_msg "${log_pre}Partitioning UBI volumes"
    if [ "$dry_run" != "y" ] ; then

        ubidetach -p /dev/$mtd_dev > /dev/null 2>&1
        ubiformat /dev/$mtd_dev -y > /dev/null 2>&1 || {
            log_failure_msg "${log_pre}unable to ubiformat $mtd_dev"
            return 1
        }

        local mtd_num=`ls -l /dev/$mtd_dev | tail -c2`
        ubiattach -m $mtd_num /dev/ubi_ctrl > /dev/null 2>&1 || {
            log_failure_msg "${log_pre}unable to ubiattach $mtd_dev"
            return 1
        }

        # Create ubi /boot volume
        ubimkvol /dev/ubi0 -N boot -s $(($boot_size*1024*1024)) > /dev/null 2>&1 || {
            log_failure_msg "${log_pre}unable to make ubi volume: boot"
            return 1
        }

        # Create ubi /mnt/persist volume
        ubimkvol /dev/ubi0 -N persist -s $(($persist_size*1024*1024)) > /dev/null 2>&1 || {
            log_failure_msg "${log_pre}unable to make ubi volume: persist"
            return 1
        }

        # Create two sysroot ubi volumes, splitting the remaining
        # space equally between them.
        local avail_blocks=$(cat /sys/class/ubi/ubi0/avail_eraseblocks)
        local bytes_per_block=$(cat /sys/class/ubi/ubi0/eraseblock_size)
        local half=$(( $avail_blocks / 2 * $bytes_per_block ))

        ubimkvol /dev/ubi0 -N sysroot1 -s $half > /dev/null 2>&1 || {
            log_failure_msg "${log_pre}unable to make ubi volume: sysroot1"
            return 1
        }

        # The 2nd half uses the remaining space
        ubimkvol /dev/ubi0 -N sysroot2 -m > /dev/null 2>&1 || {
            log_failure_msg "${log_pre}unable to make ubi volume: sysroot2"
            return 1
        }
    fi
    log_end_msg

    # create /boot filesystem -- disable compression, as the kernel
    # and initramfs are already compressed.
    make_ubifs "boot" "-x none" || return 1

    # create /mnt/persist
    make_ubifs "persist" || return 1

    # create the two sysroot filesystems
    make_ubifs "sysroot1" || return 1
    make_ubifs "sysroot2" || return 1
}

# This function runs in ONIE context.
#
# Common format disk routine
# $1 - should we check for bad GPT label?
common_format_disk()
{
    [ "$skip_disk_format" = "y" ] && return 0

    # For debugging uncomment the 'set -x' below.
    # set -x

    local check_gpt_init=$1

    # Dial down the printk console loglevel during provisioning as
    # some subsystems (UBI) use level KERN_NOTICE for simple
    # informational messages and the ONIE default KERN_INFO.
    echo "4 4 1 7 " > /proc/sys/kernel/printk

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

    if [ $root_type = 'tar' ]; then
        format_disk_blk $check_gpt_init
    elif [ $root_type = 'ubi-tar' ]; then
        format_disk_ubi
    else
        log_failure_msg "${log_pre}Invalid device type: $root_type"
        return 1
    fi
}

# Unwinds the mounted sysroot and sub-mounts, created when setting up
# the chroot environment.
tmp_dev=
chroot_dir=
clean_up_sysroot_chroot()
{
    for d in dev proc sys dev boot; do
        umount $chroot_dir/$d > /dev/null 2>&1
    done
    umount $tmp_dev > /dev/null 2>&1
    rmdir $tmp_dev > /dev/null 2>&1
    umount $chroot_dir > /dev/null 2>&1
    rmdir $chroot_dir > /dev/null 2>&1
}

# Prepares the specified sysroot slot for chroot-ing
#
# The chroot environment needs the /proc, /sys and /dev directories
# setup correctly.
prepare_sysroot_chroot()
{
    local slot="$1"

    # Mount destination slot sysroot and mount various things within
    # it.
    chroot_dir=$(mktemp -d)

    local sysroot_device=
    local boot_device=
    if [ $root_type = 'tar' ]; then
        sysroot_device="LABEL=${sysroot_label}$slot"
        boot_device="LABEL=$boot_label"
    elif [ $root_type = 'ubi-tar' ]; then
        sysroot_device="ubi:sysroot${slot}"
        boot_device="ubi:boot"
    else
        log_failure_msg "${log_pre}Invalid device type: $root_type"
        return 1
    fi

    mount_part $sysroot_type $sysroot_device $chroot_dir || {
        clean_up_sysroot_chroot
        return 1
    }

    # Mount boot partition within sysroot on /boot
    mount_part $boot_type $boot_device $chroot_dir/boot || {
        clean_up_sysroot_chroot
        return 1
    }

    # Mount /proc within sysroot
    mount_part proc proc $chroot_dir/proc "-o nodev,noexec,nosuid" || {
        clean_up_sysroot_chroot
        return 1
    }

    # Mount /sys within sysroot
    mount_part sysfs sysfs $chroot_dir/sys "-o nodev,noexec,nosuid" || {
        clean_up_sysroot_chroot
        return 1
    }

    # Here it would be *really* nice to bind mount /dev to
    # $chroot_dir/dev directly, but bind mounts do not work from an
    # initramfs root file system:
    # http://opensource.dyc.edu/ramdisk-vs-ramfs
    #
    # Copy /dev into a tmpfs and bind mount that.
    tmp_dev=$(mktemp -d)
    mount -t tmpfs tmp-dev $tmp_dev || {
        log_failure_msg "Problems mounting tmpfs on running grub-install"
        clean_up_sysroot_chroot
        return 1
    }

    cp -a /dev $tmp_dev
    mount --bind $tmp_dev/dev $chroot_dir/dev || {
        log_failure_msg "Problems mounting --bind $tmp_dev on $chroot_dir/dev"
        clean_up_sysroot_chroot
        return 1
    }

}

# This function is called after installing an image into a slot.
#
# - store meta information in /var/lib/cumulus/installer.
common_post_slot_install()
{

    local save_dir="$chroot_dir/var/lib/cumulus/installer"
    rm -rf $save_dir
    mkdir -p $save_dir || {
        echo "Error: Unable to create image post install directory: $save_dir"
        clean_up_sysroot_chroot
        return 1
    }

    # Save the installer image for future re-provisioning.
    cp $archive_file $save_dir/onie-installer
    cat /proc/cmdline > $save_dir/kernel_cmdline.txt
    export > $save_dir/runtime-env.txt

}

# This function runs in ONIE context.
# Sets variables used by generic save_info() routine.
arch_set_savedir()
{
    # Use the common boot volume to store the provisioning information
    savedir_type="$boot_type"
    savedir_opts="$boot_opts"
    savedir_dev="$boot_device"
    savedir_img=no
}
