#!/bin/bash

xcat_endpoint_host() {
    local endpoint="$1"
    local host
    local remainder

    case "$endpoint" in
        \[*\]:*)
            host=${endpoint#\[}
            host=${host%%\]*}
            ;;
        \[*\])
            host=${endpoint#\[}
            host=${host%\]}
            ;;
        *:*)
            remainder=${endpoint#*:}
            if [[ "$remainder" == *:* ]]; then
                # An unbracketed IPv6 literal has no unambiguous port suffix.
                host=$endpoint
            else
                host=${endpoint%%:*}
            fi
            ;;
        *)
            host=$endpoint
            ;;
    esac

    [ -n "$host" ] || return 1
    printf '%s\n' "$host"
}

xcat_is_ipv6_boot() {
    local iparg="$1"
    local netif="$2"

    case "$iparg" in
        dhcp6|*:dhcp6|*:dhcp6:*)
            return 0
            ;;
    esac

    [ -n "$netif" ] || return 1
    ip -6 -o addr show dev "$netif" scope global 2>/dev/null | grep -q . || return 1
    ! ip -4 -o addr show dev "$netif" scope global 2>/dev/null | grep -q .
}

persist_ipv6_nm_connection() {
    local newroot="$1"
    local netif="$2"
    local mac="$3"
    shift 3

    local destination="$newroot/etc/NetworkManager/system-connections"
    local source
    local profile
    local copied=0
    local address_cidr
    local gateway
    local sources=("$@")

    if [ ${#sources[@]} -eq 0 ]; then
        sources=(
            /run/NetworkManager/system-connections
            /etc/NetworkManager/system-connections
        )
    fi

    mkdir -p "$destination"
    for source in "${sources[@]}"; do
        [ -d "$source" ] || continue
        for profile in "$source"/*.nmconnection; do
            [ -f "$profile" ] || continue
            cp -p "$profile" "$destination/"
            chmod 600 "$destination/${profile##*/}"
            copied=1
        done
    done

    if [ "$copied" -eq 0 ]; then
        address_cidr=$(ip -6 -o addr show dev "$netif" scope global 2>/dev/null |
            awk '$3 == "inet6" { print $4; exit }')
        [ -n "$address_cidr" ] || return 1
        gateway=$(ip -6 route show default dev "$netif" 2>/dev/null |
            awk '$1 == "default" && $2 == "via" { print $3; exit }')

        profile="$destination/xcat-$netif.nmconnection"
        cat > "$profile" <<EOF
[connection]
id=xcat-$netif
type=ethernet
interface-name=$netif
autoconnect=true

[ethernet]
mac-address=$mac

[ipv4]
method=disabled

[ipv6]
method=manual
address1=$address_cidr
gateway=$gateway
addr-gen-mode=default
EOF
        chmod 600 "$profile"
    fi
}

# Prints the name of the provisioning NIC, from the kernel command line.  dracut names the
# interface in ifname= and in netdev=; BOOTIF carries a MAC only, so sysfs gives the name.
xcat_boot_netif() {
    local ifname="$1"
    local netdev="$2"
    local bootif="$3"
    local sysnet="${4:-/sys/class/net}"
    local mac
    local devpath

    if [ -n "$ifname" ]; then
        printf '%s\n' "${ifname%%:*}"
        return 0
    fi

    if [ -n "$netdev" ]; then
        printf '%s\n' "$netdev"
        return 0
    fi

    [ -n "$bootif" ] || return 1
    mac=$(printf '%s' "${bootif#01-}" | tr 'A-Z-' 'a-z:')
    for devpath in "$sysnet"/*; do
        [ -r "$devpath/address" ] || continue
        if [ "$(cat "$devpath/address")" = "$mac" ]; then
            printf '%s\n' "${devpath##*/}"
            return 0
        fi
    done

    return 1
}

# Writes the IPv6 configuration of $netif into $newroot.  A node that is not IPv6-only needs
# nothing, so a non-zero status means the configuration of an IPv6-only node is lost.  The
# booted system does not repeat the DHCPv6 exchange of the initial ramdisk, and the
# reservation answers the DUID that exchange used, so such a node comes up with no address.
xcat_persist_ipv6_boot() {
    local newroot="$1"
    local netif="$2"
    local bootip="$3"
    local sysnet="${4:-/sys/class/net}"
    local sources=("${@:5}")
    local mac

    [ -n "$netif" ] || return 0
    xcat_is_ipv6_boot "$bootip" "$netif" || return 0

    mac=$(cat "$sysnet/$netif/address" 2>/dev/null)
    [ -n "$mac" ] || return 1

    persist_ipv6_nm_connection "$newroot" "$netif" "$mac" "${sources[@]}"
}

log_label="xcat.deployment"

NEWROOT=$3
RWDIR=.statelite

. /lib/dracut-lib.sh
XCAT="$(getarg XCAT=)"
XCATMASTER=$XCAT
STATEMNT="$(getarg STATEMNT=)"
rootlimit="$(getarg rootlimit=)"
xcatdebugmode="$(getarg xcatdebugmode=)"
rflags="$(getarg rootflags=)"
getarg nonodestatus
NODESTATUS=$?

MASTER="$(xcat_endpoint_host "$XCATMASTER")"
XCATIPORT="$(getarg XCATIPORT=)"
if [ $? -ne 0 ]; then
    XCATIPORT="3002"
fi

[ "$xcatdebugmode" = "1" -o "$xcatdebugmode" = "2" ] && SYSLOGHOST=() || SYSLOGHOST=(-n "$MASTER")
logger "${SYSLOGHOST[@]}" -t $log_label -p local4.info "=============deployment starting===================="
logger "${SYSLOGHOST[@]}" -t $log_label -p local4.info "Executing xcatroot to prepare for netbooting (dracut_33)..."
[ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "MASTER=$MASTER XCATIPORT=$XCATIPORT NODESTATUS=$NODESTATUS"

if [ "$NODESTATUS" != "0" ]; then
    logger "${SYSLOGHOST[@]}" -t $log_label -p local4.info "Sending request to $MASTER:$XCATIPORT for changing status to netbooting..."
    /tmp/updateflag "$MASTER" "$XCATIPORT" "installstatus netbooting"
fi


# Downloads the checksum file published for the image at $1 to $2.
rootimg_fetch_checksum() {
    rm -f "$2"
    rootimg_status=$(curl -s -o "$2" -w '%{http_code}' "$1.sha256")
    [ "$rootimg_status" = 200 ]
}

# Prints why the file $2 cannot be used, unless it matches the SHA-256 checksum published for the image at $1.
rootimg_matches_checksum() {
    command -v sha256sum >/dev/null 2>&1 || { echo "this initial ramdisk has no sha256sum to check $1"; return 1; }
    rootimg_fetch_checksum "$1" /tmp/rootimg.sha256 || { echo "cannot download $1.sha256 (HTTP $rootimg_status)"; return 1; }
    rootimg_sum=$(sha256sum <"$2") || { echo "sha256sum cannot read $2"; return 1; }
    # While packimage replaces the archive, the checksum file lists the new and the previous archive.
    # A download that began before the replacement matches the checksum file fetched before it.
    cat /tmp/rootimg.sha256 /tmp/rootimg.sha256.before >/tmp/rootimg.sums 2>/dev/null
    while read -r rootimg_expected rootimg_name; do
        [ "${rootimg_sum%% *}" = "$rootimg_expected" ] && return 0
    done </tmp/rootimg.sums
    echo "$1 does not match $1.sha256"
    return 1
}

# Downloads the image at $1 to the file $2, over UFTP the first time when nodeset asks for it.
rootimg_download() {
    if [ -n "$rootimg_uftp" ]; then
        rootimg_uftp_port=$rootimg_uftp
        rootimg_uftp=
        # nodeset asks for UFTP only with the checksum, which the caller checks on the received file.
        if [ "$rootimgchecksum" = yes ] && [ -r /lib/xcat-rootimg-fetch.sh ] && . /lib/xcat-rootimg-fetch.sh &&
            xcat_uftp_rootimg "$1" "$2" /tmp/rootimg.sha256.before "$rootimg_uftp_port" "$(getarg xcat.uftpfp=)"; then
            return 0
        fi
        logger "${SYSLOGHOST[@]}" -t $log_label -p local4.warning "Cannot receive $1 over UFTP, downloading it over HTTP..."
    fi
    curl --fail "$1" -o "$2"
}

rootimgchecksum="$(getarg rootimgchecksum=)"
rootimg_uftp="$(getarg xcat.uftp=)"

imgurl="$(getarg imgurl=)";
if [ ! -z "$imgurl" ]; then
    if [ xhttp = x${imgurl%%:*} ] || [ xhttps = x${imgurl%%:*} ]; then
        logger "${SYSLOGHOST[@]}" -t $log_label -p local4.info "Downloading rootfs image from $imgurl..."
        NFS=0
        FILENAME=${imgurl##*/}
        [ "$rootimgchecksum" = yes ] || logger "${SYSLOGHOST[@]}" -t $log_label -p local4.info "nodeset did not ask for a check of $FILENAME, so it is not checked."
        # A file that an earlier attempt left is checked like a new download.
        if [ -r "$FILENAME" ] && [ "$rootimgchecksum" = yes ] && ! rootimg_matches_checksum "$imgurl" "$FILENAME" >/dev/null; then
            rm -f $FILENAME
        fi
        while [ ! -r "$FILENAME" ]; do
            [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "Downloading $imgurl..."
            echo "Getting $imgurl..."
            [ "$rootimgchecksum" != yes ] || rootimg_fetch_checksum "$imgurl" /tmp/rootimg.sha256.before || rm -f /tmp/rootimg.sha256.before
            if ! rootimg_download "$imgurl" "$FILENAME"; then
                logger "${SYSLOGHOST[@]}" -t $log_label -p local4.error "Downloading $imgurl FAILED, retrying..."
                rm -f "$FILENAME"
                echo Failed to get the image, waiting for next retrying...
                sleep 27
            elif [ "$rootimgchecksum" = yes ] && ! rootimg_check=$(rootimg_matches_checksum "$imgurl" "$FILENAME"); then
                logger "${SYSLOGHOST[@]}" -t $log_label -p local4.error "$rootimg_check, retrying..."
                rm -f $FILENAME
                echo "$rootimg_check, waiting for next retrying..."
                sleep 27
            fi
        done
        [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "Download complete."
    elif [ xnfs = x${imgurl%%:*} ]; then
        NFS=1
        SERVER=${imgurl#nfs:}
        SERVER=${SERVER#/}
        SERVER=${SERVER#/}
        ROOTDIR=$SERVER
        SERVER=${SERVER%%/*}
        SERVER=${SERVER%:}
        ROOTDIR=/${ROOTDIR#*/}
        [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "SERVER=$SERVER ROOTDIR=$ROOTDIR"
    fi
fi
#echo 0 > /proc/sys/vm/zone_reclaim_mode #Avoid kernel bug

if [ -r /rootimg.sfs ]; then
    echo Setting up squashfs with ram overlay.
    mknod /dev/loop0 b 7 0
    mkdir -p /ro
    mkdir -p /rw
    mount -t squashfs /rootimg.sfs /ro
    mount -t tmpfs rw /rw
    modprobe overlay
    if [ $? -eq 0 ]; then
        echo "Mounting $NEWROOT with type overlay"
        mkdir -p /rw/upper
        mkdir -p /rw/work
        mount -t overlay -o lowerdir=/ro,upperdir=/rw/upper,workdir=/rw/work mergedroot $NEWROOT
    else
        echo "Mounting $NEWROOT with type aufs"
        mount -t aufs -o dirs=/rw:/ro mergedroot $NEWROOT
        mkdir -p $NEWROOT/ro
        mkdir -p $NEWROOT/rw
        mount --move /ro $NEWROOT/ro
        mount --move /rw $NEWROOT/rw
    fi
elif [ -r /rootimg.cpio.gz ] || [ -r /rootimg.cpio.xz ]; then
    logger "${SYSLOGHOST[@]}" -t $log_label -p local4.info "Setting up RAM-root tmpfs on downloaded rootimg.cpio.[gz/xz]..."
    echo Setting up RAM-root tmpfs.
    rootopts="mode=755"
    if [ -n "$rflags" ]; then
        rootopts="$rootopts","$rflags"
    fi
    if [ -z "$rootlimit" ];then
        mount -t tmpfs -o $rootopts rootfs $NEWROOT
    else
        mount -t tmpfs -o "$rootopts",size=$rootlimit rootfs $NEWROOT
    fi

    cd $NEWROOT
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "Extracting the root filesystem..."
    echo "Extracting the root filesystem..."
    if [ -r /rootimg.cpio.gz ]; then
        if [ -x /bin/cpio ]; then
            gzip -cd /rootimg.cpio.gz |/bin/cpio -idum
        else
            gzip -cd /rootimg.cpio.gz |cpio -idum
        fi
    elif [ -r /rootimg.cpio.xz ]; then
        if [ -x /bin/cpio ]; then
            xz -cd /rootimg.cpio.xz |/bin/cpio -idum
        else
            xz -cd /rootimg.cpio.xz |cpio -idum
        fi
    fi
    if [ -x "$NEWROOT/etc/init.d/localdisk" ]; then
        "$NEWROOT/etc/init.d/localdisk"
    fi
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "Done extracting the root filesystem..."
    echo "Done extracting the root filesystem..."
elif [ -r /rootimg.tar.gz ] || [ -r /rootimg.tar.xz ]; then
    logger "${SYSLOGHOST[@]}" -t $log_label -p local4.info "Setting up RAM-root tmpfs on downloaded rootimg.tar.[gz/xz]..."
    echo Setting up RAM-root tmpfs.
    if [ -z $rootlimit ];then
        mount -t tmpfs -o mode=755 rootfs $NEWROOT
    else
        mount -t tmpfs -o mode=755,size=$rootlimit rootfs $NEWROOT
    fi

    cd $NEWROOT
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "Extracting the root filesystem..."
    echo "Extracting the root filesystem..."
    if [ -r /rootimg.tar.gz ]; then
        tar --selinux --xattrs-include='*' -zxf /rootimg.tar.gz
        if [ $? -ne 0 ]; then
            tar --selinux -zxf /rootimg.tar.gz
        fi
    elif [ -r /rootimg.tar.xz ]; then
        tar --selinux --xattrs-include='*' -Jxf /rootimg.tar.xz
        if [ $? -ne 0 ]; then
            tar --selinux -Jxf /rootimg.tar.xz
        fi
    fi
    if [ -x "$NEWROOT/etc/init.d/localdisk" ]; then
        "$NEWROOT/etc/init.d/localdisk"
    fi
    msg="Done extracting the root filesystem."
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "$msg"
    echo "$msg"
elif [ -r /rootimg-statelite.gz ]; then
    msg="Setting up RAM-root tmpfs for statelite mode."
    logger "${SYSLOGHOST[@]}" -t $log_label -p local4.info "$msg"
    echo "$msg"

    if [ -z $rootlimit];then
        mount -t tmpfs -o mode=755 rootfs $NEWROOT
    else
        mount -t tmpfs -o mode=755,size=$rootlimit rootfs $NEWROOT
    fi

    cd $NEWROOT
    msg="Extracting root filesystem..."
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "$msg"
    echo "$msg"
    if [ -x /bin/cpio ]; then
        gzip -cd /rootimg-statelite.gz |/bin/cpio -idum
    else
        gzip -cd /rootimg-statelite.gz |cpio -idum
    fi
    msg="Done extracting the root filesystem."
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "$msg"
    echo "$msg"
    # then, the statelite staffs will be processed
    msg="Setting up Statelite..."
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "$msg"
    echo "$msg"

    modprobe nfs
    MAXTRIES=7
    ITER=0
    if [ ! -e "$NEWROOT/$RWDIR" ]; then
        echo ""
        echo "The /$RWDIR directory doesn't exist in the rootimg... "
        echo ""
        logger "${SYSLOGHOST[@]}" -t $log_label -p local4.err "The /$RWDIR directory doesn't exist in the rootimg..."
        /bin/sh
    fi

    if [ ! -e "$NEWROOT/etc/init.d/statelite" ]; then
        echo ""
        echo "$NEWROOT/etc/init.d/statelite doesn't exist... "
        echo ""
        logger "${SYSLOGHOST[@]}" -t $log_label -p local4.err "$NEWROOT/etc/init.d/statelite doesn't exist... "
        /bin/sh
    fi

    mount -t tmpfs rw $NEWROOT/$RWDIR
    mkdir -p $NEWROOT/$RWDIR/tmpfs
    ME=`hostname -s`
    if [ ! -z $NODE ]; then
        ME=$NODE
    fi


    # mount the SNAPSHOT directory here for persistent use.
    if [ ! -z $STATEMNT ]; then
        SNAPSHOTSERVER=${STATEMNT%:*}
        SNAPSHOTROOT=${STATEMNT#*/}
        if [ -z $SNAPSHOTROOT ]; then
            SNAPSHOTROOT=$SNAPSHOTSERVER
            SNAPSHOTSERVER=
        fi
    fi

    if [ ! -z $SNAPSHOTSERVER ]; then
        mkdir -p $NEWROOT/$RWDIR/persistent
        MAXTRIES=5
        ITER=0
        if [ -z $MNTOPTS ]; then
            MNT_OPTIONS="nolock,rsize=32768,tcp,timeo=14"
        else
            MNT_OPTIONS=$MNTOPTS
        fi
        while ! mount $SNAPSHOTSERVER:/$SNAPSHOTROOT $NEWROOT/$RWDIR/persistent -o $MNT_OPTIONS; do
            ITER=$(( ITER + 1 ))
            if [ "$ITER" == "$MAXTRIES" ]; then
                msg="You are dead, rpower $ME boot to play again.
                     Possible problems:
    1.  $SNAPSHOTSERVER is not exporting $SNAPSHOTROOT ?
    2.  Is DNS set up? Maybe that's why I can't mount $SNAPSHOTSERVER."
                echo "$msg"
                logger "${SYSLOGHOST[@]}" -t $log_label -p local4.err "$msg"
                /bin/sh
                exit
            fi
            RS=$(( $RANDOM % 20 ))
            echo "Trying again in $RS seconds ..."
            sleep $RS
        done

        # create directory which is named after my node name
        mkdir -p $NEWROOT/$RWDIR/persistent/$ME
        ITER=0
        # umount current persistent mount
        while ! umount -l $NEWROOT/$RWDIR/persistent; do
            ITER=$(( ITER + 1 ))
            if [ "$ITER" == "$MAXTRIES" ]; then
                msg="Your are dead, rpower $ME boot to play again.
                     Cannot umount $NEWROOT/$RWDIR/persistent."
                echo "$msg"
                logger "${SYSLOGHOST[@]}" -t $log_label -p local4.err "$msg"
                /bin/sh
                exit
            fi
            RS=$(( $RANDOM % 20 ))
            echo "Trying again in $RS seconds..."
            sleep $RS
        done

        # mount persistent to server:/rootpath/nodename
        ITER=0
        while ! mount $SNAPSHOTSERVER:/$SNAPSHOTROOT/$ME  $NEWROOT/$RWDIR/persistent -o $MNT_OPTIONS; do
            ITER=$(( ITER + 1 ))
            if [ "$ITER" == "$MAXTRIES" ]; then
                msg="Your are dead, rpower $ME boot to play again.
                     Possible problems: cannot mount to $SNAPSHOTSERVER:/$SNAPSHOTROOT/$ME."
                echo "$msg"
                logger "${SYSLOGHOST[@]}" -t $log_label -p local4.err "$msg"
                /bin/sh
                exit
            fi
            RS=$(( $RANDOM % 20 ))
            echo "Trying again in $RS seconds..."
            sleep $RS
        done
    fi

    logger "${SYSLOGHOST[@]}" -t $log_label -p local4.info "Enabling localdisk ..."
    $NEWROOT/etc/init.d/localdisk
    logger "${SYSLOGHOST[@]}" -t $log_label -p local4.info "Preparing mount points ..."
    $NEWROOT/etc/init.d/statelite
    fastboot=yes
    export fastboot
    keep_old_ip=yes
    export keep_old_ip

    mount -n --bind /dev $NEWROOT/dev
    mount -n --bind /proc $NEWROOT/proc
    mount -n --bind /sys $NEWROOT/sys

else
    msg="Failed to download image, panic in 5..."
    logger "${SYSLOGHOST[@]}" -t $log_label -p local4.error "$msg"
    echo -n "$msg"
    for i in 4 3 2 1 0; do
        /bin/sleep 1
        [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "$i..."
        echo -n $i...
    done
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "You're dead.  rpower nodename reset to play again."
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "* Did you packimage with -m cpio, -m squashfs, or -m nfs?"
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "* If using -m squashfs did you include aufs.ko with geninitrd? e.g.:  -n tg3,squashfs,aufs,loop"
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "If using -m nfs did you export NFS and sync rootimg?  And did you include the aufs and nfs modules in the proper order: e.g.:  -n tg3,aufs,loop,sunrpc,lockd,nfs_acl,nfs"

    echo
    echo "You're dead.  rpower nodename reset to play again.

* Did you packimage with -m cpio, -m squashfs, or -m nfs?
* If using -m squashfs did you include aufs.ko with geninitrd?
  e.g.:  -n tg3,squashfs,aufs,loop
* If using -m nfs did you export NFS and sync rootimg?  And
  did you include the aufs and nfs modules in the proper order:
  e.g.:  -n tg3,aufs,loop,sunrpc,lockd,nfs_acl,nfs

"
    /bin/sh
    exit
fi
cd /

[ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "Saving $NEWROOT/etc/resolv.conf"
cp /etc/resolv.conf "$NEWROOT/etc/"

if [ -d "$NEWROOT/etc/sysconfig" -a ! -e "$NEWROOT/etc/sysconfig/selinux" ]; then
    [ "$xcatdebugmode" > "0" ] && logger -t $log_label -p local4.debug "disable selinux ..."
    echo "SELINUX=disabled" >> "$NEWROOT/etc/sysconfig/selinux"
fi

if [ -z "$STATEMNT" ]; then
    BOOT_NETIF="$(xcat_boot_netif "$(getarg ifname=)" "$(getarg netdev=)" "$(getarg BOOTIF=)")"
    if ! xcat_persist_ipv6_boot "$NEWROOT" "$BOOT_NETIF" "$(getarg ip=)"; then
        logger "${SYSLOGHOST[@]}" -t $log_label -p local4.error "Unable to persist the static IPv6 configuration for $BOOT_NETIF"
        exit 1
    fi
fi

logger "${SYSLOGHOST[@]}" -t $log_label -p local4.info "Exiting xcatroot..."
# inject new exit_if_exists
echo 'settle_exit_if_exists="--exit-if-exists=/dev/root"; rm "$job"' > $hookdir/initqueue/xcat.sh
# force udevsettle to break
> $hookdir/initqueue/work
