#!/bin/bash
# This file is part of curtin. See LICENSE file for copyright and license info.

TEMP_D=""
CR="
"
VERBOSITY=${VERBOSITY:-${CURTIN_VERBOSITY:-0}}

error() { echo "$@" 1>&2; }
debug() {
    [ ${VERBOSITY:-0} -ge "$1" ] || return
    shift
    error "$@"
}

partition_main_usage() {
    cat <<EOF
Usage: ${0##*/} [ options ] target-dev

   partition target-dev with a single partition
   destroy any partition table that might be there already.

   options:
     -f | --format F   use partition table format F. [mbr, gpt, uefi, prep]
                       default mbr
     -E | --end E      end the partition at E (unit 1k bytes)
     -b | --boot       create a boot partition (512 MiB - default)
EOF
    [ $# -eq 0 ] || echo "$@"
}

grub_install_usage() {
    cat <<EOF
Usage: ${0##*/} [ options ] mount-point target-dev

   perform grub-install with mount-point onto target-dev.

   options:
          --uefi           install grub-efi instead of grub-pc
          --update-nvram   request grub to update nvram
EOF
    [ $# -eq 0 ] || echo "$@"
}

cleanup() {
    if [ -d "$TEMP_D" ]; then
        rm -Rf "$TEMP_D"
    fi
}


wipe_partitions() {
    # wipe_partition(blockdev, ptno)
    local dev="" target="" ret="" part="" full="" out=""
    if [ "$1" = "--full" ]; then
        full="--full"
        shift
    fi
    dev="$1"
    shift
    for part in "$@"; do
        find_partno "$dev" $part ||
            { ret=$?; error "did not find $part on $dev"; return $ret; }
        target="$_RET"
        wipedev $full "$target" false false ||
            { ret=$?; error "failed to wipe $part on $dev"; return $ret; }
    done
    return 0
}

wipedev() {
    # wipedev([--full,] target, wipe_end=true, reread=true)
    # wipe the front and optionally end of $target
    # if reread=true call rereadpt and settle
    local full="0"
    if [ "$1" = "--full" ]; then
        full="1"
        shift
    fi
    local target="$1" wipe_end=${2:-true} rereadpt=${3:-true}
    local size="" out="" bs="" count="" seek="" mb=$((1024*1024))
    local info=""
    getsize "$target" ||
        { error "failed to get size of $target"; return 1; }
    size="$_RET"

    # select a block size that evenly divides size. bigger is generally faster.
    for bs in $mb 4096 1024 512 1; do
        [ "$((size % bs))" = "0" ] && break
    done
    if [ "$bs" = "1" ]; then
        error "WARN: odd sized '$target' ($size). not divisible by 512."
    fi

    if [ "$full" != "0" ]; then
        count=$((size / bs))
        info="size=$size conv=notrunc count=$count bs=$bs"
        debug 1 "wiping full '$target' with ${info}."
        out=$(LANG=C dd if=/dev/zero conv=notrunc "of=$target" \
            bs=$bs count=$count 2>&1) || {
            error "wiping entire '$target' with ${info} failed."
            error "$out"
            return 1
        }
    else
        local fbs=$bs
        count=$((size / bs))
        if [ "$size" -ge "$mb" ]; then
            count=1
            fbs=$mb
        fi
        info="size=$size count=$count bs=$fbs"
        debug 1 "wiping start of '$target' with ${info}."
        # wipe the first MB (up to 'size')
        out=$(dd if=/dev/zero conv=notrunc "of=$target" \
                "bs=$fbs" "count=$count" 2>&1) || {
            error "wiping start of '$target' with ${info} failed."
            error "$out"
            return 1
        }

        if $wipe_end && [ "$size" -gt "$mb" ]; then
            # do the last 1MB
            count=$((mb / bs))
            seek=$(((size / bs) - $count))
            info="size=$size count=$count bs=$bs seek=$seek"
            debug 1 "wiping end of '$target' with ${info}."
            out=$(dd if=/dev/zero conv=notrunc "of=$target" "seek=$seek" \
                "bs=$bs" "count=$count" 2>&1)
            if [ $? -ne 0 ]; then
                error "wiping end of '$target' with ${info} failed."
                error "$out";
                return 1;
            fi
        fi
    fi

    if $rereadpt && [ -b "$target" ]; then
        blockdev --rereadpt "$target"
        udevadm settle
    fi
}

find_partno() {
    local devname="$1" partno="$2"
    local devbname cand msg="" slash="/"
    devbname="${devname#/dev/}"
    # /dev/cciss/c0d0 -> ccis!c0d0
    devbname="${devbname//$slash/!}"
    if [ -d "/sys/class/block/${devbname}" ]; then
        local cand candptno name partdev
        debug 1 "using sys/class/block/$devbname"
        for cand in /sys/class/block/$devbname/*/partition; do
            [ -f "$cand" ] || continue
            read candptno < "$cand"
            [ "$candptno" = "$partno" ] || continue
            name=${cand#/sys/class/block/${devbname}/}
            name=${name%/partition}
            # ccis!c0d0p1 -> ccis/c0d0p1
            name=${name//!/$slash}
            partdev="/dev/$name"
            [ -b "$partdev" ] && _RET="$partdev" && return 0
            msg="expected $partdev to exist as partition $partno on $devname"
            error "WARN: $msg. it did not exist."
        done
    else
        for cand in "${devname}$partno" "${devname}p${partno}"; do
            [ -b "$cand" ] && _RET="$cand" && return 0
        done
    fi
    return 1
}

part2bd() {
    # part2bd given a partition, return the block device it is on
    # and the number the partition is.  ie, 'sda2' -> '/dev/sda 2'
    local dev="$1" fp="" sp="" bd="" ptnum=""
    dev="/dev/${dev#/dev/}"
    fp=$(readlink -f "$dev") || return 1
    sp="/sys/class/block/${fp##*/}"
    [ -f "$sp/partition" ] || { _RET="$fp 0"; return 0; }
    read ptnum < "$sp/partition"
    sp=$(readlink -f "$sp") || return 1
    # sp now has some /sys/devices/pci..../0:2:0:0/block/sda/sda1
    bd=${sp##*/block/}
    bd="${bd%/*}"
    _RET="/dev/$bd $ptnum"
    return 0
}

pt_gpt() {
    local target="$1" end=${2:-""} boot="$3" size="" s512=""
    local start="2048" rootsize="" bootsize="1048576" maxend=""
    local isblk=false
    getsize "$target" ||
        { error "failed to get size of $target"; return 1; }
    size="$_RET"
    if [ -z "$end" ]; then
        end=$(($size/512))
    else
        end=$(($end/512))
    fi

    if [ "$boot" = true ]; then
        maxend=$((($size/512)-$start-$bootsize))
        if [ $maxend -lt 0 ]; then
            error "Disk is not big enough for /boot partition on $target";
            return 1;
        fi
    else
        maxend=$((($size/512)-$start))
    fi
    [ "$end" -gt "$maxend" ] && end="$maxend"
    debug 1 "maxend=$maxend end=$end size=$size"

    [ -b "$target" ] && isblk=true

    if [ "$boot" = true ]; then
        # Creating 'efi', '/boot' and '/' partitions
        sgdisk --new "15:$start:+1M" --typecode=15:ef02 \
            --new "1::+512M" --typecode=1:8300 \
            --new "2::$end" --typecode=2:8300 "$target" ||
            { error "failed to gpt partition $target"; return 1; }
    else
        # Creating 'efi' and '/' partitions
        sgdisk --new "15:$start:+1M" --typecode=15:ef02 \
            --new "1::$end" --typecode=1:8300 "$target" ||
            { error "failed to gpt partition $target"; return 1; }
    fi

    if $isblk; then
        local expected="1 15"
        [ "$boot" = "true" ] && expected="$expected 2"
        blockdev --rereadpt "$target"
        udevadm settle
        assert_partitions "$target" $expected ||
            { error "$target missing partitions: $_RET"; return 1; }
        wipe_partitions "$target" $expected ||
            { error "$target: failed to wipe partitions"; return 1; }
    fi
}

assert_partitions() {
    local dev="$1" missing="" part=""
    shift
    for part in "$@"; do
        find_partno "$dev" $part || missing="${missing} ${part}"
    done
    _RET="${missing# }"
    [ -z "$missing" ]
}

pt_uefi() {
    local target="$1" end=${2:-""} size="" s512=""
    local start="2048" rootsize="" maxend=""
    local isblk=false
    getsize "$target" ||
        { error "failed to get size of $target"; return 1; }
    size="$_RET"
    if [ -z "$end" ]; then
        end=$(($size/512))
    else
        end=$(($end/512))
    fi

    maxend=$((($size/512)-$start))
    [ "$end" -gt "$maxend" ] && end="$maxend"
    debug 1 "maxend=$maxend end=$end size=$size"

    [ -b "$target" ] && isblk=true

    # Creating 'UEFI' and '/' partitions
    sgdisk --new "15:2048:+512M" --typecode=15:ef00 \
           --new "1::$end" --typecode=1:8300 "$target" ||
        { error "failed to sgdisk for uefi to $target"; return 1; }

    if $isblk; then
        blockdev --rereadpt "$target"
        udevadm settle
        assert_partitions "$target" 1 15 ||
            { error "$target missing partitions: $_RET"; return 1; }
        wipe_partitions "$target" 1 15 ||
            { error "$target: failed to wipe partitions"; return 1; }
    fi

    local pt15
    find_partno "$target" 15 && pt15="$_RET" ||
        { error "failed to find partition 15 for $target"; return 1; }
    mkfs -t vfat -F 32 -n uefi-boot "$pt15" ||
        { error "failed to partition :$pt15' for UEFI vfat"; return 1; }
}


pt_mbr() {
    local target="$1" end=${2:-""} boot="$3" size="" s512="" ptype="L"
    local start="2048" rootsize="" maxsize="4294967296"
    local maxend="" isblk=false def_bootsize="1048576" bootsize=0
    local isblk=false
    getsize "$target" ||
        { error "failed to get size of $target"; return 1; }
    size="$_RET"

    if $boot; then
        bootsize=$def_bootsize
    fi

    s512=$(($size/512))
    if [ $s512 -ge $maxsize ]; then
        debug 1 "disk is larger than max for mbr (2TB)"
        s512=$maxsize
    fi

    # allow 33 sectors for the secondary gpt header in the case that
    # the user wants to later 'sgdisk --mbrtogpt'
    local gpt2hsize="33"
    if [ -n "$end" ]; then
        rootsize=$(((end/512)-start-bootsize))
    else
        rootsize=$((s512-start-bootsize-$gpt2hsize))
    fi

    [ -b "$target" ] && isblk=true

    # interact with sfdisk in units of 512 bytes (--unit S)
    # we start all partitions at 2048 of those (1M)
    local sfdisk_out="" sfdisk_in="" sfdisk_cmd="" t="" expected=""
    if "$boot"; then
        t="$start,$bootsize,$ptype,-${CR}"
        t="$t$(($start+$bootsize)),$rootsize,$ptype,*"
        sfdisk_in="$t"
        expected="1 2"
    else
        sfdisk_in="$start,$rootsize,$ptype,*"
        expected=1
    fi
    sfdisk_cmd=( sfdisk --no-reread --force --Linux --unit S "$target" )
    debug 1 "sfdisking with: echo '$sfdisk_in' | ${sfdisk_cmd[*]}"
    sfdisk_out=$(echo "$sfdisk_in" | "${sfdisk_cmd[@]}" 2>&1)
    ret=$?
    [ $ret -eq 0 ] || {
        error "failed to partition $target [${sfdisk_out}]";
        return 1;
    }
    if $isblk; then
        blockdev --rereadpt "$target"
        udevadm settle
        assert_partitions "$target" ${expected} ||
            { error "$target missing partitions: $_RET"; return 1; }

        wipe_partitions "$target" ${expected} ||
            { error "failed to wipe partition 1 on $target"; return 1; }
    fi

}

pt_prep() {
    local target="$1" end=${2:-""}
    local cmd="" isblk=false
    [ -b "$target" ] && isblk=true

    local pprep="1" proot="2"
    wipedev "$target" ||
        { error "failed to clear $target"; return 1; }

    cmd=(
        sgdisk
           --new "${pprep}::+8M"  "--typecode=${pprep}:4100"
           --new "${proot}::$end" "--typecode=${proot}:8300"
           "$target"
    )
    debug 1 "partitioning '$target' with ${cmd[*]}"
    "${cmd[@]}" ||
        fail "Failed to create GPT partitions (${cmd[*]})"

    udevadm trigger
    udevadm settle

    if $isblk; then
        blockdev --rereadpt "$target"
        udevadm settle
        assert_partitions "$target" "${proot}" "${pprep}"  ||
            { error "$target missing partitions: $_RET"; return 1; }
        # wipe the full prep partition
        wipe_partitions --full "$target" "${pprep}" ||
            { error "$target: failed to wipe full PReP partition"; return 1;}
        wipe_partitions "$target" "${proot}" ||
            { error "$target: failed to wipe partition ${proot}"; return 1;}
    fi

    return 0
}

partition_main() {
    local short_opts="hE:f:bv"
    local long_opts="help,end:,format:,boot,verbose"
    local getopt_out=$(getopt --name "${0##*/}" \
        --options "${short_opts}" --long "${long_opts}" -- "$@") &&
        eval set -- "${getopt_out}" ||
        { partition_main_usage 1>&2; return 1; }

    local cur="" next=""
    local format="mbr" boot=false target="" end="" ret=0

    while [ $# -ne 0 ]; do
        cur="$1"; next="$2";
        case "$cur" in
            -h|--help) partition_main_usage ; exit 0;;
            -E|--end) end=$next; shift;;
            -f|--format) format=$next; shift;;
            -b|--boot) boot=true;;
            -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
            --) shift; break;;
        esac
        shift;
    done

    [ $# -gt 1 ] && { partition_main_usage "got $# args, expected 1" 1>&2; return 1; }
    [ $# -eq 0 ] && { partition_main_usage "must provide target-dev" 1>&2; return 1; }
    target="$1"
    if [ -n "$end" ]; then
        human2bytes "$end" ||
            { error "failed to convert '$end' to bytes"; return 1; }
        end="$_RET"
    fi

    [ "$format" = "gpt" -o "$format" = "mbr" ] ||
        [ "$format" = "uefi" -o "$format" = "prep" ] ||
        { partition_main_usage "invalid format: $format" 1>&2; return 1; }

    TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
        fail "failed to make tempdir"
    trap cleanup EXIT

    [ -e "$target" ] || { error "$target does not exist"; return 1; }
    [ -f "$target" -o -b "$target" ] ||
        { error "$target not a block device"; return 1; }

    wipedev "$target" ||
        { error "wiping $target failed"; return 1; }

    if [ "$format" = "mbr" ]; then
        pt_mbr "$target" "$end" "$boot"
    elif [ "$format" = "gpt" ]; then
        pt_gpt "$target" "$end" "$boot"
    elif [ "$format" = "uefi" ]; then
        pt_uefi "$target" "$end"
    elif [ "$format" = "prep" ]; then
        pt_prep "$target" "$end"
    fi
    ret=$?

    return $ret
}

human2bytes() {
    # converts size suitable for input to resize2fs to bytes
    # s:512 byte sectors, K:kilobytes, M:megabytes, G:gigabytes
    # none: block size of the image
    local input=${1} defunit=${2:-1024}
    local unit count;
    case "$input" in
        *s) count=${input%s}; unit=512;;
        *K) count=${input%K}; unit=1024;;
        *M) count=${input%M}; unit=$((1024*1024));;
        *G) count=${input%G}; unit=$((1024*1024*1024));;
        *)  count=${input}  ; unit=${defunit};;
    esac
   _RET=$((${count}*${unit}))
}

getsize() {
    # return size of target in bytes
    local target="$1"
    if [ -b "$target" ]; then
        _RET=$(blockdev --getsize64 "$target")
    elif [ -f "$target" ]; then
        _RET=$(stat "--format=%s" "$target")
    else
        return 1;
    fi
}

is_md() {
    case "${1##*/}" in
        md[0-9]) return 0;;
    esac
    return 1
}

get_carryover_params() {
    local cmdline=" $1 " extra="" lead="" carry_extra="" carry_lead=""
    # return a string to append to installed systems boot parameters
    # it may include a '--' after a '---'
    # see LP: 1402042 for some history here.
    # this is similar to 'user-params' from d-i
    local preferred_sep="---"  # KERNEL_CMDLINE_COPY_TO_INSTALL_SEP
    local legacy_sep="--"
    case "$cmdline" in
        *\ ${preferred_sep}\ *)
            extra=${cmdline#* ${preferred_sep} }
            lead=${cmdline%% ${preferred_sep} *}
            ;;
        *\ ${legacy_sep}\ *)
            extra="${cmdline#* ${legacy_sep} }"
            lead=${cmdline%% ${legacy_sep} *}
            ;;
        *)
            extra=""
            lead="$cmdline"
            ;;
    esac

    if [ -n "$extra" ]; then
        carry_extra=$(set -f;
            c="";
            for p in $extra; do
                case "$p" in
                    (BOOTIF=*|initrd=*|BOOT_IMAGE=*) continue;;
                esac
                c="$c $p";
            done
            echo "${c# }"
        )
    fi

    # these get copied even if they werent after the separator
    local padded=" $carry_extra "
    carry_lead=$(set -f;
        padded=" ${carry_extra} "
        c=""
        for p in $lead; do
            # skip any that are already in carry_extra
            [ "${padded#* $p }" != "$padded" ] && continue
            case "$p" in
                (console=*) c="$c $p";;
            esac
        done
        echo "${c# }"
    )
    [ -n "${carry_lead}" -a -n "${carry_extra}" ] &&
        carry_lead="${carry_lead} "
    _RET="${carry_lead}${carry_extra}"
}

shell_config_update() {
    # shell_config_update(file, name, value)
    # update variable 'name' setting value to 'val' in shell syntax 'file'.
    # if 'name' is not present, then append declaration.
    local file="$1" name="$2" val="$3"
    if ! [ -f "$file" ] || ! grep -q "^$name=" "$file"; then
        debug 2 "appending to $file shell $name=\"$val\""
        echo "$name=\"$val\"" >> "$file"
        return
    fi
    local cand="" del=""
    for cand in "|" "," "/"; do
        [ "${val#*${del}}" = "${val}" ] && del="$cand" && break
    done
    [ -n "$del" ] || {
        error "Couldn't find a sed delimiter for '$val'";
        return 1;
    }

    sed -i -e "s${del}^$name=.*${del}$name=\"$val\"${del}" "$file" ||
        { error "Failed editing '$file' to set $name=$val"; return 1; }
    debug 2 "updated $file to set $name=\"$val\""
    return 0
}

apply_grub_cmdline_linux_default() {
    local mp="$1" newargs="$2" edg="${3:-etc/default/grub}"
    local gcld="GRUB_CMDLINE_LINUX_DEFAULT"
    debug 1 "setting $gcld to '$newargs' in $edg"
    shell_config_update "$mp/$edg" "$gcld" "$newargs" || {
        error "Failed to set '$gcld=$newargs' in $edg"
        return 1
    }
}

get_parent_disk() {
    # Look up the parent /dev path via sysfs.  Using the partition
    # kname (nvme0n1p1), construct a /sys/class/block path, use
    # realpath to resolve this to an absolute path which includes
    # the parent:
    #   /sys/devices/pci0000:00/*/*/nvme/nvme0/nvme0n1/nvme0n1p1
    # dirname to extract the parent, then read the 'dev' entry
    #   /sys/devices/pci0000:00/*/*/nvme/nvme0/nvme0n1/dev
    # which contains the MAJOR:MINOR value and construct a /dev/block
    # path which is a symbolic link that udev constructs that points
    # to the real device name and use realpath to return the absolute path.
    #   /dev/block/259:0 -> ../nvme0n1
    local devpath="${1}"
    local kname=$(basename "$devpath")
    local syspath=$(realpath "/sys/class/block/$kname")
    local disksyspath=$(dirname "$syspath")
    local diskmajmin=$(cat "${disksyspath}/dev")
    local diskdevpath=$(realpath "/dev/block/${diskmajmin}")
    echo $diskdevpath
}

install_grub() {
    local long_opts="uefi,update-nvram,os-family:"
    local getopt_out="" mp_efi=""
    getopt_out=$(getopt --name "${0##*/}" \
        --options "" --long "${long_opts}" -- "$@") &&
        eval set -- "${getopt_out}"

    local uefi=0 update_nvram=0 os_family=""

    while [ $# -ne 0 ]; do
        cur="$1"; next="$2";
        case "$cur" in
            --os-family) os_family=${next};;
            --uefi) uefi=$((${uefi}+1));;
            --update-nvram) update_nvram=$((${update_nvram}+1));;
            --) shift; break;;
        esac
        shift;
    done

    [ $# -lt 2 ] && { grub_install_usage "must provide mount-point and target-dev" 1>&2; return 1; }

    local mp="$1"
    local cmdline tmp r=""
    shift
    local grubdevs
    grubdevs=( "$@" )
    if [ "${#grubdevs[@]}" = "1" -a "${grubdevs[0]}" = "none" ]; then
        grubdevs=( )
    fi
    debug 1 "grubdevs: [${grubdevs[@]}]"

    # find the mp device
    local mp_dev="" fstype=""
    mp_dev=$(awk -v "MP=$mp" '$2 == MP { print $1 }' /proc/mounts) || {
        error "unable to determine device for mount $mp";
        return 1;
    }
    debug 1 "/proc/mounts shows $mp_dev is mounted at $mp"

    fstype=$(awk -v MP=$mp '$2 == MP { print $3 }' /proc/mounts) || {
        error "unable to fstype for mount $mp";
        return 1;
    }

    [ -z "$mp_dev" ] && {
        error "did not find '$mp' in /proc/mounts"
        cat /proc/mounts 1>&2
        return 1
    }
    # check if parsed mount point is a block device
    # error unless fstype is zfs, where entry will not point to block device.
    if ! [ -b "$mp_dev" ] && [ "$fstype" != "zfs" ]; then
        # error unless mp is zfs, entry doesn't point to block devs
        error "$mp_dev ($fstype) is not a block device!"; return 1;
    fi

    local os_variant=""
    if [ -e "${mp}/etc/os-release" ]; then
        os_variant=$(chroot "$mp" \
                     /bin/sh -c 'echo $(. /etc/os-release; echo $ID)')
    else
        # Centos6 doesn't have os-release, so check for centos/redhat release
        # looks like: CentOS release 6.9 (Final)
        for rel in $(ls ${mp}/etc/*-release); do
            os_variant=$(awk '{print tolower($1)}' $rel)
            [ -n "$os_variant" ] && break
        done
    fi
    [ $? != 0 ] &&
        { error "Failed to read ID from $mp/etc/os-release"; return 1; }

    local rhel_ver=""
    case $os_variant in
        debian|ubuntu) os_family="debian";;
        centos|rhel)
            os_family="redhat"
            rhel_ver=$(chroot "$mp" rpm -E '%rhel')
        ;;
    esac

    # ensure we have both settings, family and variant are needed
    [ -n "${os_variant}" -a -n "${os_family}" ] ||
        { error "Failed to determine os variant and family"; return 1; }

    # get target arch
    local target_arch="" r="1"
    case $os_family in
        debian)
            target_arch=$(chroot "$mp" dpkg --print-architecture)
            r=$?
            ;;
        redhat)
            target_arch=$(chroot "$mp" rpm -E '%_arch')
            r=$?
            ;;
    esac
    [ $r -eq 0 ] || {
        error "failed to get target architecture [$r]"
        return 1;
    }

    # grub is not the bootloader you are looking for
    if [ "${target_arch}" = "s390x" ]; then
        return 0;
    fi

    # set correct grub package
    local grub_name=""
    local grub_target=""
    case "$target_arch" in
        i386|amd64)
            # debian
            grub_name="grub-pc"
            grub_target="i386-pc"
            ;;
        x86_64)
            case $rhel_ver in
               6) grub_name="grub";;
               7|8) grub_name="grub2-pc";;
               *)
                   error "Unknown rhel_ver [$rhel_ver]";
                   return 1;
               ;;
            esac
            grub_target="i386-pc"
            ;;
    esac
    if [ "${target_arch#ppc64}" != "${target_arch}" ]; then
        grub_name="grub-ieee1275"
        grub_target="powerpc-ieee1275"
    elif [ "$uefi" -ge 1 ]; then
        grub_name="grub-efi-$target_arch"
        case "$target_arch" in
            x86_64)
                # centos 7+, no centos6 support
                # grub2-efi-x64 installs a signed grub bootloader while
                # curtin uses grub2-efi-x64-modules to generate grubx64.efi.
                # Either works just check that one of them is installed.
                grub_name="grub2-efi-x64 grub2-efi-x64-modules"
                grub_target="x86_64-efi"
                ;;
            amd64)
                grub_target="x86_64-efi";;
            arm64)
                grub_target="arm64-efi";;
        esac
    fi

    # check that the grub package is installed
    local r=$?
    case $os_family in
        debian)
            tmp=$(chroot "$mp" dpkg-query --show \
                --showformat='${Status}\n' $grub_name)
            r=$?
            ;;
        redhat)
            tmp=$(chroot "$mp" rpm -q \
                --queryformat='install ok installed\n' $grub_name)
            r=$?
            ;;
    esac
    if [ $r -ne 0 -a $r -ne 1 ]; then
        error "failed to check if $grub_name installed";
        return 1;
    fi
    # Check that any of the packages in $grub_name are installed. If
    # grub_name contains multiple packages, as it does for CentOS 7+,
    # only one package has to be installed for this to pass.
    if ! echo $tmp | grep -q 'install ok installed'; then
        debug 1 "$grub_name not installed, not doing anything"
        return 1
    fi

    local grub_d="etc/default/grub.d"
    # ubuntu writes to /etc/default/grub.d/50-curtin-settings.cfg
    # to avoid tripping prompts on upgrade LP: #564853
    local mygrub_cfg="$grub_d/50-curtin-settings.cfg"
    case $os_family in
        redhat)
            grub_d="etc/default"
            mygrub_cfg="etc/default/grub";;
    esac
    [ -d "$mp/$grub_d" ] || mkdir -p "$mp/$grub_d" ||
        { error "Failed to create $grub_d"; return 1; }

    # LP: #1179940 . The 50-cloudig-settings.cfg file is written by the cloud
    # images build and defines/override some settings. Disable it.
    local cicfg="$grub_d/50-cloudimg-settings.cfg"
    if [ -f "$mp/$cicfg" ]; then
       debug 1 "moved $cicfg out of the way"
       mv "$mp/$cicfg" "$mp/$cicfg.disabled"
    fi

    # get the user provided / carry-over kernel arguments
    local newargs=""
    read cmdline < /proc/cmdline &&
        get_carryover_params "$cmdline" && newargs="$_RET" || {
        error "Failed to get carryover parrameters from cmdline"; 
        return 1;
    }
    # always append rd.auto=1 for centos
    case $os_family in
        redhat)
            newargs="${newargs:+${newargs} }rd.auto=1";;
    esac
    debug 1 "carryover command line params '$newargs'"

    if [ "${REPLACE_GRUB_LINUX_DEFAULT:-1}" != "0" ]; then
        apply_grub_cmdline_linux_default "$mp" "$newargs" || {
            error "Failed to apply grub cmdline."
            return 1
        }
    fi

    if [ "${DISABLE_OS_PROBER:-1}" == "1" ]; then
        {
            echo "# Curtin disable grub os prober that might find other OS installs."
            echo "GRUB_DISABLE_OS_PROBER=true"
        } >> "$mp/$mygrub_cfg"
    fi

    if [ -n "${GRUB_TERMINAL}" ]; then
        {
            echo "# Curtin configured GRUB_TERMINAL value"
            echo "GRUB_TERMINAL=${GRUB_TERMINAL}"
        } >> "$mp/$mygrub_cfg"
    fi

    debug 1 "processing grubdevs values for expansion if needed"
    local short="" bd="" grubdev grubdevs_new=""
    grubdevs_new=()
    for grubdev in "${grubdevs[@]}"; do
        if is_md "$grubdev"; then
            debug 1 "$grubdev is raid, find members"
            short=${grubdev##*/}
            for bd in "/sys/block/$short/slaves/"/*; do
                [ -d "$bd" ] || continue
                bd=${bd##*/}
                bd="/dev/${bd%[0-9]}" # FIXME: part2bd
                debug 1 "Add dev $bd to grubdevs_new"
                grubdevs_new[${#grubdevs_new[@]}]="$bd"
            done
        else
            debug 1 "Found dev [$grubdev] add to grubdevs_new"
            grubdevs_new[${#grubdevs_new[@]}]="$grubdev"
        fi
    done
    grubdevs=( "${grubdevs_new[@]}" )
    debug 1 "updated grubdevs: [${grubdevs[@]}]"

    if [ "$uefi" -ge 1 ]; then
        nvram="--no-nvram"
        if [ "$update_nvram" -ge 1 ]; then
            nvram=""
        fi
        debug 1 "number of entries in grubdevs_new: ${#grubdevs[@]}"
        if [ "${#grubdevs_new[@]}" -eq 1 ] && [ -b "${grubdevs_new[0]}" ]; then
            debug 1 "Found a single entry in grubdevs, ${grubdevs_new[0]}"
            # Currently UEFI can only be pointed to one system partition. If
            # for some reason multiple install locations are given only use the
            # first.
            efi_dev="${grubdevs_new[0]}"
            debug 1 "efi_dev=[${efi_dev}]"
        elif [ "${#grubdevs_new[@]}" -gt 1 ]; then
            error "Only one grub device supported on UEFI!"
            exit 1
        else
            debug 1 "no storage config, parsing /proc/mounts with awk"
            # If no storage configuration was given try to determine the system
            # partition.
            efi_dev=$(awk -v "MP=${mp}/boot/efi" '$2 == MP { print $1 }' /proc/mounts)
            debug 1 "efi_dev=[${efi_dev}]"
            [ -n "$efi_dev" ] || {
                error "Failed to find efi device from parsing /proc/mounts"
                return 1
            }

        fi
        # The partition number of block device name need to be determined here
        # so both getting the UEFI device from Curtin config and discovering it
        # work.
        efi_part_num=$(cat /sys/class/block/$(basename $efi_dev)/partition)
        debug 1 "efi_part_num: $efi_part_num"
        [ -n "${efi_part_num}" ] || {
            error "Failed to determine $efi_dev partition number"
            return 1
        }
        efi_disk=$(get_parent_disk "$efi_dev")
        debug 1 "efi_disk: [$efi_disk]"
        [ -b "${efi_disk}" ] || {
            error "${efi_disk} is not a valid block device"
            return 1
        }
        debug 1 "curtin uefi: installing ${grub_name} to: /boot/efi"
        chroot "$mp" env DEBIAN_FRONTEND=noninteractive sh -exc '
            echo "before grub-install efiboot settings"
            efibootmgr -v || echo "WARN: efibootmgr exited $?"
            bootid="$4"
            efi_disk="$5"
            efi_part_num="$6"
            grubpost=""
            grubmulti="/usr/lib/grub/grub-multi-install"
            case $bootid in
                debian|ubuntu)
                    grubcmd="grub-install"
                    if [ -e "${grubmulti}" ]; then
                        grubcmd="${grubmulti}"
                    fi
                    dpkg-reconfigure "$1"
                    update-grub
                    ;;
                centos|redhat|rhel)
                    grubcmd="grub2-install"
                    # RHEL uses redhat instead of the os_variant rhel for the bootid.
                    if [ "$bootid" = "rhel" ]; then
                        bootid="redhat"
                    fi
                    if [ -f /boot/efi/EFI/$bootid/grubx64.efi ]; then
                        grubpost="grub2-mkconfig -o /boot/efi/EFI/$bootid/grub.cfg"
                    else
                        grubpost="grub2-mkconfig -o /boot/grub2/grub.cfg"
                    fi
                    ;;
                *)
                    echo "Unsupported OS: $bootid" 1>&2
                    exit 1
                    ;;
            esac
            # grub-install in 12.04 does not contain --no-nvram, --target,
            # or --efi-directory
            target="--target=$2"
            no_nvram="$3"
            efi_dir="--efi-directory=/boot/efi"
            gi_out=$($grubcmd --help 2>&1)
            echo "$gi_out" | grep -q -- "$no_nvram" || no_nvram=""
            echo "$gi_out" | grep -q -- "--target" || target=""
            echo "$gi_out" | grep -q -- "--efi-directory" || efi_dir=""

            # Do not overwrite grubx64.efi if it already exists. grub-install
            # generates grubx64.efi and overwrites any existing binary in
            # /boot/efi/EFI/$bootid. This binary is not signed and will cause
            # secure boot to fail.
            #
            # CentOS, RHEL, Fedora ship the signed boot loader in the package
            # grub2-efi-x64 which installs the signed boot loader to
            # /boot/efi/EFI/$bootid/grubx64.efi. All Curtin has to do is
            # configure the firmware. This mirrors what Anaconda does.
            #
            # Debian and Ubuntu come with a patched version of grub which
            # add the install flag --uefi-secure-boot which is enabled by
            # default. When enabled if a signed version of grub exists on
            # the filesystem it will be copied into /boot/efi/EFI/$bootid.
            # Stock Ubuntu images do not ship with anything in /boot. Those
            # files are generated by installing a kernel and grub.
            echo "Dumping /boot/efi contents"
            find /boot/efi
            echo "Checking for existing EFI grub entry on ESP"
            if [ "$grubcmd" = "grub2-install" -a -f /boot/efi/EFI/$bootid/grubx64.efi ]; then
                if [ -z "$no_nvram" ]; then
                    # UEFI firmware should be pointed to the shim if available to
                    # enable secure boot.
                    for boot_uefi in \
                            /boot/efi/EFI/$bootid/shimx64.efi \
                            /boot/efi/EFI/BOOT/BOOTX64.EFI \
                            /boot/efi/EFI/$bootid/grubx64.efi; do
                        if [ -f $boot_uefi ]; then
                            break
                        fi
                    done
                    loader=$(echo ${boot_uefi##/boot/efi} | sed "s|/|\\\|g")
                    efibootmgr --create --write-signature --label $bootid \
                        --disk $efi_disk --part $efi_part_num --loader $loader
                    rc=$?
                    [ "$rc" != "0" ] && { exit $rc; }
                else
                    echo "skip EFI entry creation due to \"$no_nvram\" flag"
                fi
            else
                echo "No previous EFI grub entry found on ESP, use $grubcmd"
                if [ "${grubcmd}" = "${grubmulti}" ]; then
                    $grubcmd
                else
                    $grubcmd $target $efi_dir \
                        --bootloader-id=$bootid --recheck $no_nvram
                fi
            fi
            [ -z "$grubpost" ] || $grubpost;' \
            -- "$grub_name" "$grub_target" "$nvram" "$os_variant" "$efi_disk" "$efi_part_num" </dev/null ||
            { error "failed to install grub!"; return 1; }

        chroot "$mp" sh -exc '
            echo "after grub-install efiboot settings"
            efibootmgr -v || echo "WARN: efibootmgr exited $?"
            ' -- </dev/null ||
            { error "failed to list efi boot entries!"; return 1; }
    else
        # Note: dpkg-reconfigure calls grub-install on ppc64
        # this means that using '--no-nvram' below ends up
        # failing very oddly.  This is because grub's post-inst
        # runs grub-install with no target.  That ends up
        # updating nvram badly, and then the grub-install would
        # not fix it because of the no-nvram there.
        debug 1 "curtin non-uefi: installing ${grub_name} to: ${grubdevs[*]}"
        chroot "$mp" env DEBIAN_FRONTEND=noninteractive sh -exc '
            pkg=$1; shift;
            bootid=$1; shift;
            bootver=$1; shift;
            grubpost=""
            case $bootid in
                debian|ubuntu)
                    grubcmd="grub-install"
                    dpkg-reconfigure "$pkg"
                    update-grub
                    ;;
                centos|redhat|rhel)
                    case $bootver in
                        6) grubcmd="grub-install";;
                        7|8) grubcmd="grub2-install"
                           grubpost="grub2-mkconfig -o /boot/grub2/grub.cfg";;
                        *)
                           echo "Unknown rhel_ver [$bootver]"
                           exit 1
                    esac
                    ;;
                *)
                    echo "Unsupported OS: $bootid"; 1>&2
                    exit 1
                    ;;
            esac
            for d in "$@"; do
                echo $grubcmd "$d";
                $grubcmd "$d" || exit; done
            [ -z "$grubpost" ] || $grubpost;' \
            -- "${grub_name}" "${os_variant}" "${rhel_ver}" "${grubdevs[@]}" </dev/null ||
            { error "failed to install grub!"; return 1; }
    fi

    if [ -n "${mp_efi}" ]; then
        umount "$mp_efi" ||
            { error "failed to unmount $mp_efi"; return 1; }
    fi

    return
}

# vi: ts=4 expandtab syntax=sh
