#!/bin/bash

set -eu

# A simple tool to grab and extract debian-installer netboot images.
#
# Copyright (C) 2008 Frank Lin PIAT <fpiat@klabs.be>
# latest version is available from:
#     http://wiki.debian.org/DebianInstaller/NetbootAssistant
#
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St - Suite 330, Boston, MA 02110, USA.


# ------------------ Declare the constants ------------------- #
PACKAGE_NAME=di-netboot-assistant
PACKAGE_VERSION=0.50


# -------------- Initialize the global variables ------------- #
OFFLINE=false
VERBOSE=false
DEBUG=false
DISOURCELIST=/etc/di-netboot-assistant/di-sources.list
SYSLINUX=/usr/lib/syslinux/
DL_CACHE=/var/cache/di-netboot-assistant
STATUS_LIB=/var/lib/di-netboot-assistant
TEMPLATES=/etc/di-netboot-assistant
TFTP_ROOT=/var/lib/tftpboot
N_A_DIR=d-i/n-a  # where di-netboot-assistant images are set up
DI_PKG_DIR=d-i/n-pkg  # where the debian-installer-*-netboot-* image is copied or bind mounted
CLI_ALIAS=""
REWRITEPKGPATH='\(debian\|ubuntu\)-installer'
CHECKSUM_FILE="SHA256SUMS"
CHECKSUM_BIN="sha256sum"
DEBIAN_KEYRING="/usr/share/keyrings/debian-archive-keyring.gpg"
UBUNTU_KEYRING="/usr/share/keyrings/ubuntu-archive-keyring.gpg"
DI_ARGS=
TARGET_ARGS=
ARCH=
DEFAULT_ARCH=""
#MIRROR_REGEXPS=# Not defined on purpose, so user can pass the variable
umask $(umask | sed -e 's/.$/2/')              # files must be public.

if [ -f "$HOME/.di-netboot-assistant/di-netboot-assistant.conf" ]; then
    . "$HOME/.di-netboot-assistant/di-netboot-assistant.conf"
else
    if [ -f "/etc/di-netboot-assistant/di-netboot-assistant.conf" ]; then
	. "/etc/di-netboot-assistant/di-netboot-assistant.conf"
    fi
fi


# ------------------- Declare the functions ------------------ #


# ------------------------------------------------------------ #
# usage()
#	Print script usage help.
# Parameters: release
# Returns: (EXIT STATUS) 0=Success
# ------------------------------------------------------------ #
usage() {
	cat <<XXX
Usage: $PACKAGE_NAME [options] install DI-DIST [--offline] [--arch=ARCH] [--alias=NAME]
       $PACKAGE_NAME [options] [purge|uninstall|uncache] DI-DIST [--arch=ARCH]
       $PACKAGE_NAME [options] rebuild-menu
       $PACKAGE_NAME [--help|--version|--rebuild-menu]

A simple tool to grab and extract debian-installer netboot images.

DI-DIST
	The name of a debian-installer repository (as listed in
	the file '$DISOURCELIST')

Commands:
    install      - download and extract a netboot image.
    uninstall    - remove a previously installed netboot image.
    uncache      - remove downloaded files from the cache.
    purge        - equivalent to uninstall plus uncache.
    rebuild-menu - rebuild the top level menu.
    rebuild-grub - rebuild the grub EFI netboot image.

Options:
  -h, --help      Print this message and exit
  -V, --version   Print script version and exit
  -v, --verbose   Verbose messages
  --offline       Don't download the file (simply re-extract and build menu)
  --di-args=      DI arguments to be appended to "install" entry.
  --target-args=  Target system boot arguments to be appended to "install".
  --alias=NAME    Rename the downloaded repository (optional).
  --arch=ARCH     A comma separated list of architecture to Install/Purge,
                  or the keyword "all". It use the current machine's
                  architecture by default.

See the $PACKAGE_NAME(1) manual page for more information.
XXX
}


# ------------------------------------------------------------ #
# detect_current_arch()
#	Detect's the system's current architecture
# Parameters: none
# Returns: (STRING) architecture
# ------------------------------------------------------------ #
detect_current_arch() {
    local s
    if which dpkg >/dev/null 2>&1; then
	dpkg --print-architecture
    elif  which rpm >/dev/null 2>&1; then
	s=$(rpm --eval "%{_arch}")
	s=$(tr -d " " < /usr/lib/rpm/rpmrc | grep "^buildarchtranslate:$a:")
	s=$(echo $s|cut -d: -f3)
	s=$(echo $s | sed -e 's/^x86_64$/amd64/' -e 's/^sparc[0-9]*$/sparc/' -e 's/ppc[0-9]*$/powerpc/' -e 's/^armv[3456]*$/armel/' -e 's/^armv7hl$/armhf/' -e 's/^m68kmint$/m68k/')
	echo $s
    else
	echo "i386"
    fi
}

# ------------------------------------------------------------ #
# check_di_source_list()
#	Check the validity of di-source.list
# Parameters: release
# Returns: (EXIT STATUS) 0=Success, 1=Error
# ------------------------------------------------------------ #
check_di_source_list() {
    local valid_regex='^(#.*|[[:blank:]]*|[[:alnum:]_\\.-]+[	][[:alnum:]_\\.-]+[	][^	]+([	][^" ]+)+)$'

    if [ ! -f "$DISOURCELIST" ]; then
	echo "E: Debian Installer source file missing ($DISOURCELIST)" 1>&2
	return 1
    fi

    if grep -qvEl "$valid_regex"  "$DISOURCELIST" ; then
	echo -n "E: Syntax error lines #"  1>&2
	grep -vnE "$valid_regex" "$DISOURCELIST" \
	    | cut -d ":" -f 1 | tr "\n" "," 1>&2
	echo " in file '$DISOURCELIST'." 1>&2
	return 1
    fi
    return 0
}


# ------------------------------------------------------------ #
# list_declared_arch_for_repo()
#	List archs declared for the repository in di-sources.list
# Parameters: repository
# Returns: (STRING) List of architectures
# ------------------------------------------------------------ #
list_declared_arch_for_repo() {
    local release=$1

    echo -n "I: Declared architecures for $1 are: " 1>&2
    get_declared_arch_for_repo "$release" | tr '\n' ' ' 1>&2
    echo "" 1>&2
}


# ------------------------------------------------------------ #
# get_declared_arch_for_repo()
#	List archs declared for the repository in di-sources.list
# Parameters: repository
# Returns: (STRING) List of architectures
# ------------------------------------------------------------ #
get_declared_arch_for_repo() {
    local release=$1

    if [ "$1" ]; then
	grep -E "^$release\>" "$DISOURCELIST" | cut -f 2 | sort -u
    fi
    echo -n ""
}


# ------------------------------------------------------------ #
# print_do_not_edit_header()
#	Print a "Do no edit this file" warning
# Parameters: templatename
# Returns: (STRING) file header comment
# ------------------------------------------------------------ #
print_do_not_edit_header() {
    local templatename=$1		# Template filename

    echo "##"
    echo "## DO NOT EDIT THIS FILE"
    echo "##"
    echo "## It is automatically generated by '$PACKAGE_NAME'"
    echo "## using '$templatename' as template."
    echo "##"
}


# ------------------------------------------------------------ #
# find_file()
#	Return the name of the first file matching criteria.
# Parameters: name dir [dir...]
# Returns: (STRING) file
# ------------------------------------------------------------ #
find_file() {
    if [ "$1" -a "$2" ]; then
	local name=$1; shift
	find "$@" -type f -name "$name" | head -n 1
    else
	echo ""
    fi
}


# ------------------------------------------------------------ #
# version_lte()
#	Compare two "software" version (like 1.2.1 and 1.3)
# Parameters: V1 V2
# Returns: (EXIT STATUS) 0=v1 <= v2, 1= V1 > V2
# ------------------------------------------------------------ #
version_lte() {
    if which dpkg > /dev/null 2>&1; then
	dpkg --compare-versions "$1" "<=" "$2"
	return $?
    else
	printf "$1\n$2\n" | sort -V | head -n 1 | grep -q "^$1\$"
	return $?
    fi
}


# ------------------------------------------------------------ #
# prepare_grub()
#	Install grub-EFI.
# Parameters: opt
# Returns: (EXIT STATUS) 0=Success, 1=Error
# ------------------------------------------------------------ #
prepare_grub() {
    local v="" opt=$1 efidir="/usr/lib/grub/x86_64-efi/"

    $VERBOSE && v="-v"
    [ -z "$opt"  ] && [ -d $TFTP_ROOT/$N_A_DIR/grub -a \
                           -e $TFTP_ROOT/$N_A_DIR/bootnetx64.efi ] && return 0

    if ! which grub-mknetdir > /dev/null ; then
        echo "W: 'grub-mknetdir' is not available, no EFI support provided." 1>&2
        return 0
    elif [ ! -d "$efidir" ] ; then
        echo "W: No '$efidir' found, no EFI support available." 1>&2
        echo "W: Install 'grub-efi-amd64-bin' to gain EFI support." 1>&2
        return 0
    fi

    echo "I: Preparing EFI grub image."
    if grub-mknetdir --net-directory=$TFTP_ROOT --subdir=$N_A_DIR/grub -d "$efidir" ; then
        ln -v -srf $TFTP_ROOT/$N_A_DIR/grub/x86_64-efi/core.efi \
           $TFTP_ROOT/$N_A_DIR/bootnetx64.efi
        ln $v -srf $TFTP_ROOT/$N_A_DIR/grub/fonts/unicode.pf2 \
           $TFTP_ROOT/$N_A_DIR/grub/font.pf2
    else
        echo "E: Preparing grub-netboot image failed." 1>&2
        return 1
    fi
    return 0
}


# ------------------------------------------------------------ #
# copy_syslinux_bin()
#	Install pxelinux binaries in the target folder.
# Parameters: src dst
# Returns: (EXIT STATUS) 0=Success, 1=Error
# ------------------------------------------------------------ #
copy_syslinux_bin() {
    local src=$1			# Source directory
    local dst=$2			# Target directory
    local c32_dir=$dst/pxelinux.cfg
    local f srcf oldbin newbin pxe_new_ver pxe_cur_ver

    [ ! "$src" -o ! "$dst" ] && return 1

    if [ "$SYSLINUX" = "$src" ]; then
	# avoid recent SYSLINUX EFI binaries incompatible with PXELINUX
	[ ! -d "$src/modules/bios" ] || src="$src/modules/bios"
	# recent SYSLINUX ships PXELINUX at separate location
	newbin=$(find_file pxelinux.0 /usr/lib/PXELINUX "$SYSLINUX" 2>/dev/null)
    else
	newbin=$(find_file pxelinux.0 "$src" 2>/dev/null)
    fi
    [ ! -f "$dst/pxelinux.0" -a ! -f "$newbin" ] && return 1

    pxe_new_ver="$(pxelinux_version "$newbin")"
    pxe_cur_ver="$(pxelinux_version "$dst/pxelinux.0")"
    if version_lte "$pxe_new_ver" "$pxe_cur_ver"; then
	return 0
    fi

    if [ -n "$pxe_cur_ver" ] && [ -n "$pxe_new_ver" ] ; then
        echo "I: Upgrading PXE-Linux ($pxe_cur_ver to $pxe_new_ver)"
    else
        echo "I: Installing PXE-Linux ($pxe_new_ver)"
    fi

    for f in pxelinux.0 menu.c32 vesamenu.c32; do
	if [ pxelinux.0 = "$f" ]; then
	    srcf="$newbin"
	else
	    srcf="$(find_file $f "$src")"
	fi
	[ "${f#*c32}" ] || f="pxelinux.cfg/$f"
	[ -L "$dst/$f" ] && rm "$dst/$f"
	if [ -f "$srcf" ]; then
	    cp "$srcf" "$dst/$f"
	else
	    [ -f "$dst/$f" ] && rm "$dst/$f"
	fi
    done
    # Smooth transition to vesamenu
    [ ! -f "$c32_dir/menu.c32" ] && ln -s "vesamenu.c32" $c32_dir/menu.c32
    # Add core modules at root (see <https://bugs.debian.org/756275#49>)
    if [ "$TFTP_ROOT/$N_A_DIR/" = "$dst" ] ; then
	for f in ldlinux.c32 libcom32.c32 libutil.c32 ; do
	    srcf="$(find_file $f "$src")"
	    [ -z "$srcf" ] || cp -np "$srcf" "$TFTP_ROOT/$N_A_DIR/$f"
	done
    fi
    return 0
}


# ------------------------------------------------------------ #
# update_menu()
#	Create the bootloader's top menu.
# Parameters: (NONE)
# Returns: (EXIT STATUS) 0
# ------------------------------------------------------------ #
update_menu() {
    if [ ! -d "$TFTP_ROOT/$N_A_DIR" ] ; then
        if [ ! -d "$TFTP_ROOT/$DI_PKG_DIR" ] ; then
            return
        else
            mkdir "$TFTP_ROOT/$N_A_DIR"
        fi
    fi
    cd "$TFTP_ROOT/$N_A_DIR"

    update_pxe_grub_menu
    include_installer_packages
    prepare_grub ""

    if find "$TFTP_ROOT/$N_A_DIR" -mindepth 1 -type d | grep -q "." || \
            [ -d $TFTP_ROOT/$DI_PKG_DIR ] ; then
	sed -e 's/^\s*//' > "$TFTP_ROOT/$N_A_DIR/README.txt" <<xREADMEx
                !!! Attention !!!
                All files in this folder are managed by 'di-netboot-assistant'.

                Any modification and any added file may be removed
                at any time by 'di-netboot-assistant'.
xREADMEx
    else
	rm "$TFTP_ROOT/$N_A_DIR/README.txt" > /dev/null 2>&1 || true
    fi
    rmdir --ignore-fail-on-non-empty "$TFTP_ROOT/$N_A_DIR"
    return 0
}


# ------------------------------------------------------------ #
# update_pxe_grub_menu()
#	Create PXElinux and grub-EFI bootloader top menu.
# Parameters: (NONE)
# Returns: (EXIT STATUS) 0
# ------------------------------------------------------------ #
update_pxe_grub_menu() {
    local x i s

    echo "I: Building menu entries for the netboot-images."
    [ ! -d "pxelinux.cfg" ] && mkdir "pxelinux.cfg"
    print_do_not_edit_header "$TEMPLATES/pxelinux.HEAD" > pxelinux.cfg/default
    [ ! -d "grub" ] && mkdir -p "grub"
    print_do_not_edit_header "$TEMPLATES/grub.cfg.HEAD" > grub/grub.cfg

    if [ -n "$(find "$TFTP_ROOT/$N_A_DIR" -type d -name pxelinux.cfg.serial-9600 2>/dev/null)" ]; then
        echo -e "CONSOLE 0\nSERIAL 0 9600" > pxelinux.cfg/default.serial-9600
        cat pxelinux.cfg/default >> pxelinux.cfg/default.serial-9600
    else
	[ -f "pxelinux.cfg/default.serial-9600" ] && rm pxelinux.cfg/default.serial-9600
    fi
    [ -f $TEMPLATES/pxelinux.HEAD ] && grep -Ev "^##" $TEMPLATES/pxelinux.HEAD >> pxelinux.cfg/default
    [ -f $TEMPLATES/grub.cfg.HEAD ] && grep -Ev "^##" $TEMPLATES/grub.cfg.HEAD >> grub/grub.cfg

    i=0
    for x in $(ls "$STATUS_LIB/"*.pxelinux.menu.fragment 2>/dev/null ); do
        i=$(($i + 1))
        grep -Ev "^##" $x >> pxelinux.cfg/default >> pxelinux.cfg/default
        echo -n "I:  • "
        grep -E  "^[[:space:]]*MENU BEGIN" $x | sed -e "s/.*MENU BEGIN[[:space:]]\+//"
    done

    if s=$(find . -path "*/stable/*/boot-screens/splash.png" | head -1) && [ -n "$s" ] ; then
        echo -e "MENU BACKGROUND ::/$N_A_DIR${s#.}\n" >> pxelinux.cfg/default
        echo "I: Using splash screen from 'stable' image."
    elif [ -d $TFTP_ROOT/$DI_PKG_DIR ] && \
             s=$(find $TFTP_ROOT/$DI_PKG_DIR -name "splash.png" | head -1) && [ -n "$s" ] ; then
        echo -e "MENU BACKGROUND ::${s#$TFTP_ROOT}\n" >> pxelinux.cfg/default
        echo "I: Using splash screen from debian-installer-*-netboot-* package."
    else
        echo "I: Splash screen not found.  Install 'stable' to use its splash screen."
    fi

    [ $i -eq 0 ] && [ ! -d $TFTP_ROOT/$DI_PKG_DIR ] && rm pxelinux.cfg/default
    rm pxelinux.cfg/default.mig-bak 2>/dev/null || true

    i=0
    for x in $(ls "$STATUS_LIB/"*.grub.menu.fragment 2>/dev/null ); do
        i=$(($i + 1))
        grep -Ev "^##" $x >> grub/grub.cfg
    done
    [ $i -eq 0 ] && [ ! -d $TFTP_ROOT/$DI_PKG_DIR ] && rm grub/grub.cfg

    i=0
    if [ -f "pxelinux.cfg/default.serial-9600" ]; then
        i=$(($i + 1))
        [ -f $TEMPLATES/pxelinux.HEAD ] && cat $TEMPLATES/pxelinux.HEAD >> pxelinux.cfg/default.serial-9600
        for x in "$STATUS_LIB/"*pxelinux.menu.serial-9600.fragment ; do
            grep -Ev "^##" "$x" >> pxelinux.cfg/default.serial-9600
            echo "" >> pxelinux.cfg/default.serial-9600
        done
        [ $i -eq 0 ] && rm pxelinux.cfg/default.serial-9600
    fi
    return 0
}


# ------------------------------------------------------------ #
# include_installer_packages()
#	Create PXElinux bootloader menu for installed debian-installer-*-netboot-* packages.
# Parameters: (NONE)
# Returns: (EXIT STATUS) 0
# ------------------------------------------------------------ #
include_installer_packages() {
    local x gcfg ngcfg title relpath
    if [ ! -e "$TFTP_ROOT/$N_A_DIR/pxelinux.0" ] ; then
        copy_syslinux_bin "$SYSLINUX" "$TFTP_ROOT/$N_A_DIR/" || \
            copy_syslinux_bin "${TFTP_ROOT}/${DI_PKG_DIR}" "$TFTP_ROOT/$N_A_DIR/" || \
            echo "E: No PXE binaries found and installed." 1>&2
    fi
    echo "I: Building menu entries for debian-installer-*-netboot-* packages."

    echo -e "MENU SEPARATOR\n" >> pxelinux.cfg/default
    for x in $(ls ${TFTP_ROOT}/${DI_PKG_DIR}/images/*/*/*/version.info 2>/dev/null ); do
        relpath=$(dirname "$x" | sed -e "s#${TFTP_ROOT}##" -e "s#^/*##")
        title="$(cat "$x"|tr -d "\n"|sed -e "s#Installer##" -e "s# version: ##" \
                -e "s#build: ##") $(echo $relpath | sed -re "s#^.+/(\w+/\w+)\$#\1#")"
	cat >> pxelinux.cfg/default <<EOF
LABEL $title
        MENU LABEL $title
        CONFIG ::${relpath}/pxelinux.cfg/default ::${relpath}/

EOF
        gcfg="${TFTP_ROOT}/${relpath}/$N_A_DIR/amd64/grub/grub.cfg"
        if [ -f "$gcfg" ] ; then
            ## We do not want to modify the packaged installer images, copy grub.cfg instead:
            ngcfg="grub/grub-${relpath//'/'/'_'}.cfg"
            print_do_not_edit_header "$gcfg" > "${TFTP_ROOT}/$N_A_DIR/$ngcfg"
            sed -e "s#$REWRITEPKGPATH#$relpath/$N_A_DIR#" "$gcfg" >> "$ngcfg"
            sed -e "s#/gtk/#/text/#g" "$ngcfg" > "${ngcfg//'gtk'/'text'}"
            cat >> grub/grub.cfg <<EOF
menuentry '$title' {
    configfile /$N_A_DIR/$ngcfg
}

menuentry '${title//gtk /text}' {
    configfile /$N_A_DIR/${ngcfg//gtk/text}
}

EOF
        fi
        echo "I:  • ${title}"
    done

    if [ -f pxelinux.cfg/default ]; then
	for x in $(sed -n -e "s,^\s*KERNEL\s[\s:/]*\(.*menu.c32\).*,\1,p " \
                       pxelinux.cfg/default | sort -u ); do
            if [ ! -f "${TFTP_ROOT}/$x" ] ; then
                echo "W: The binary '${TFTP_ROOT}/$x' mentioned in the PXE boot menu is missing."
            fi
	done
    else
	find pxelinux.cfg/ -iregex '.*\(\.c32\|\.bak.*\|~\)$' | \
            xargs -r rm $RM_VERBOSITY
	[ -d pxelinux.cfg ] && rmdir $RM_VERBOSITY --ignore-fail-on-non-empty pxelinux.cfg
	[ ! -d pxelinux.cfg -a -e pxelinux.0 ] && rm $RM_VERBOSITY pxelinux.0
    fi
    return 0
}


# ------------------------------------------------------------ #
# check_tftp_root()
#	Check that declared TFTP root directory is valid.
# Parameters: (NONE)
# Returns: (NULL)
# ------------------------------------------------------------ #
check_tftp_root() {
    if [ -z "$TFTP_ROOT" -o "$TFTP_ROOT" = "." -o "$TFTP_ROOT" = "/" ]; then
	echo "E: Invalid TFTP root specified ($TFTP_ROOT)" 1>&2
	exit 1
    fi

    if [ ! -d "$TFTP_ROOT" ]; then
	echo "E: TFTP root directory doesn't exists ($TFTP_ROOT)" 1>&2
	echo "I: Make sure you installed a tftp server like tftpd-hpa or atftpd."
	exit 1
    fi

    [ ! -d "$TFTP_ROOT/$N_A_DIR" ] && mkdir -p "$TFTP_ROOT/$N_A_DIR"
    if [ ! -w "$TFTP_ROOT/$N_A_DIR" ]; then
	echo "E: Can't write to DI directory ($TFTP_ROOT/$N_A_DIR)" 1>&2
	exit 1
    fi
}


#This function should be kept in sync with function "uninstall_repo" in debian/postrm

# ------------------------------------------------------------ #
# uninstall_repo()
#	Remove the specfied repository.
# Parameters: dist_conf
# Returns: (EXIT STATUS) 0
# ------------------------------------------------------------ #
uninstall_repo() {
    dist_conf="$1"			# Repository's .conf file
    local s metadatabasename tarfile expand_dir dist_dir

    metadatabasename="$(echo $dist_conf | sed -e 's/\.conf$//' )"

    #remove di-netboot-assistant < 0.37 cached files.
    tarfile="$(grep -E "^[[:blank:]]*dl_file=" "$dist_conf" | sed -e 's/^[[:blank:]]*dl_file=//')"
    if [ "$(echo $tarfile | sed -n -e 's/^\(.\).*/\1/p')" = "/" ]; then
	[ -f "$tarfile" ] && rm "$tarfile"
    fi

    expand_dir="$(grep -E "^[[:blank:]]*expand_dir=" "$dist_conf" | sed -e 's/^[[:blank:]]*expand_dir=//')"
    [ "$expand_dir" != "/" -a -d "$expand_dir" ] && rm -Rf "$expand_dir"

    dist_dir="$(echo  "$expand_dir" | sed -e 's,/[^/]\+$,,')"
    rmdir $RM_VERBOSITY --ignore-fail-on-non-empty "$dist_dir"

    s="$metadatabasename.pxelinux.menu.fragment"
    [ -f "$s" ] && rm $RM_VERBOSITY "$s"

    s="$metadatabasename.grub.menu.fragment"
    [ -f "$s" ] && rm $RM_VERBOSITY "$s"

    s="$metadatabasename.pxelinux.menu.serial-9600.fragment"
    [ -f "$s" ] && rm $RM_VERBOSITY "$s"

    rm $RM_VERBOSITY "$dist_conf"
    return 0
}

# ------------------------------------------------------------ #
# get_installed_repos()
#	List the installed repositories.
# Parameters: none
# Returns: (STRINGS) Installed repos
# ------------------------------------------------------------ #
get_installed_repos() {
    find  "$STATUS_LIB/"  -name \*--\*.conf \
	| sed -e 's,^.*/,,' -e 's/--.*\.conf//' \
	| tr '\n' ' '
}


# ------------------------------------------------------------ #
# uninstall_repos()
#	Remove the specfied repository for all specified archs.
# Parameters: repo ignore_missing
# Returns: (EXIT STATUS) 0=Success, 1=Error
# ------------------------------------------------------------ #
uninstall_repos() {
    local repo="$1"			# Name of the repository
    local ignore_missing="$2"	# Don't repo.
    local a archs installed_archs installed_repos

    $DEBUG && set -x

    if [ ! -d "$STATUS_LIB" ]; then
	echo "E: Failed to uninstall repository, lib folder not found." 1>&2
	exit 1
    fi

    installed_archs="$(find  "$STATUS_LIB/"  -name \*$repo--\*.conf | sed -e 's/^.*--//' -e 's/\.conf//' | tr '\n' ' ')"
    if [ ! "$installed_archs" -a "$ignore_missing" != "ignore_missing" ]; then
	installed_repos="$(get_installed_repos)"
	echo "E: Repository '$repo' not installed." 1>&2
	echo -e "E: Installed repositories are:\n${installed_repos}" 1>&2
	exit 1
    fi

    [ ! "$ARCH" ] && ARCH=$DEFAULT_ARCH

    if [ "$(echo $ARCH | grep -E "\<all\>")" ]; then
	archs="$installed_archs"
    else
	archs="$(echo $ARCH | tr ',' ' ')"
    fi

    for a in $archs ; do
	if [ -f "$STATUS_LIB/$repo--$a.conf" ]; then
	    uninstall_repo "$STATUS_LIB/$repo--$a.conf"
	else
	    if [ "$ignore_missing" != "ignore_missing" ]; then
		echo "E: Repository '$repo' for architecture '$a' doesn't exists." 1>&2
		echo -e "E: Installed arch are:\n$(echo $installed_archs | tr "\n" " ")" 1>&2
		return 1
	    fi
	fi
    done

    $DEBUG && set +x
    return 0
}


#This function should be kept in sync with function "url2filename" in debian/postrm

# ------------------------------------------------------------ #
# url2filename()
#	Convert an URL into a valid filename.
# Parameters: (PIPE) url
# Returns: (STRING) filename
# ------------------------------------------------------------ #
url2filename() {
    sed -e 's#//\+#/#g' -e 's#[^[:alnum:]@+_~\.-]#_#g'
}


#This function should be kept in sync with function "remove_repocache" in debian/postrm

# ------------------------------------------------------------ #
# remove_repocache()
#	Remove the cached file.
# Parameters: metadatafile
# Returns: (EXIT STATUS) 0
# ------------------------------------------------------------ #
remove_repocache() {
    local metadatafile="$1"		# repository to uncache
    local base file

    base=$(echo $metadatafile | sed -e 's/~~.*$//' )

    for file in $(sed -n -e 's/^[[:blank:]]*dl_file=[[:blank:]]*//p' $metadatafile); do
	rm $RM_VERBOSITY ${base}_"$(echo $file| url2filename)"
    done

    #Purge remaing files (MD5SUMs...) if there are no more cached
    #distribution from the same repository.
    if [ ! "$(ls -1 ${base}~~*.meta | grep -v "$metadatafile")" ]; then
	rm $RM_VERBOSITY ${base}_*
    fi

    [ -f $metadatafile ] && rm $RM_VERBOSITY $metadatafile
    return 0
}


# ------------------------------------------------------------ #
# remove_repocacheq()
#	Remove the cached file.
# Parameters: metadatafile
# Returns: (EXIT STATUS) 0=Success, 1=Error
# ------------------------------------------------------------ #
remove_repocaches() {
    local del_repo="$1"
    local ignore_missing="$2"
    local count cached_archs archs metadatafile a

    $DEBUG && set -x

    if [ ! -d "$DL_CACHE" ]; then
	echo "E: Failed to clean the cache, cache folder not found." 1>&2
	exit 1
    fi

    cached_archs="$(find $DL_CACHE -name "*~~$del_repo--*.meta" | sed -e 's/^.*--//' -e 's/\.meta//' | tr '\n' ' ')"
    if [ ! "$cached_archs" -a "$ignore_missing" != "ignore_missing" ]; then
	cached_repos="$(find $DL_CACHE -name \*~~\*--\*.meta | sed -e 's/^.*~~//' -e 's/--.*//' | tr '\n' ' ')"
	echo "E: Repository '$del_repo' not cached." 1>&2
	echo "I: (cached repositories are: $cached_repos)" 1>&2
	exit 1
    fi

    [ ! "$ARCH" ] && ARCH=$DEFAULT_ARCH

    if [ "$(echo $ARCH | grep -E "\<all\>")" ]; then
	archs="$cached_archs"
    else
	archs="$(echo $ARCH | tr ',' ' ')"
    fi

    for a in $archs ; do
	count=0
	for metadatafile in $(find $DL_CACHE/ -name "*~~${del_repo}--${a}.meta"); do
	    remove_repocache "$metadatafile"
	    count=$(( $count + 1 ))
	done

	if [ $count -eq 0 -a "$ignore_missing" != "ignore_missing" ]; then
	    echo "E: Repository '$del_repo' for architecture '$a' doesn't exists." 1>&2
	    echo "I: (cached archs are: $cached_archs)" 1>&2
	    exit 1
	fi
    done

    $DEBUG && set +x
    return 0
}


# ------------------------------------------------------------ #
# check_sum()
#       Validate a file's checksum.
# Parameters: csum_file fname actual_file
# Returns: (EXIT STATUS) 0=checksum is ok, 1=checksum mismatch
# ------------------------------------------------------------ #
check_sum() {
    local csum_file=$1		# file containing checksums
    local fname=$2		# file to look for in the checksum file
    local actual_file=$3	# file to calulate checksum from
    local sum regex

    if [ ! -f "$actual_file" -o ! -f "$csum_file" ]; then
	return 1
    fi
    sum=$($CHECKSUM_BIN $actual_file | cut -d " " -f 1)
    $VERBOSE && echo -e "I: $CHECKSUM_BIN of '$actual_file':\n$sum" 1>&2
    regex="^[[:blank:]]*$sum[[:blank:]]+([[:digit:]]+[[:blank:]]+)?(\./|)$fname[[:blank:]]*$"
    if ! grep -qiE $regex $csum_file ; then
        $VERBOSE && echo "Checksum not found in '$csum_file'." 1>&2
	return 1
    elif $VERBOSE ; then
        echo "I: Checksum found in '$csum_file':"
        grep -iE $regex $csum_file
    fi
    return 0
}


# ------------------------------------------------------------ #
# check_signature()
#       Validate signature of checksum file.
# Parameters: download URL
# Returns: (EXIT STATUS) 0=signature is ok,
#                        1=signature wrong,
#                        other error codes for fatal errors
# ------------------------------------------------------------ #
check_signature() {
    local file="$1"       # downloaded checksum file
    local getter="$2"     # download program
    local baseurl="$3"    # URL of the checksum file directory
    local s dmp ret=4

    dmp=$(mktemp)

    if [[ "$url" == *"ubuntu"* ]] ; then
        if $getter "$file.gpg" -- "${baseurl}/${CHECKSUM_FILE}.gpg" ; then
            LANG=C gpgv $GPG_VERBOSITY --keyring $UBUNTU_KEYRING "$file.gpg" "$file" > "$dmp" 2>&1
            ret=$?
        else
            echo "E: Could not download '${baseurl}/${CHECKSUM_FILE}.gpg'." 1>&2
            ret=3
        fi
    else
        if $getter "$file.Release" -- "${baseurl}/../../../../Release" && \
                $getter "$file.Release.gpg" -- "${baseurl}/../../../../Release.gpg" ; then
            s=$(echo ${baseurl} | sed -E "s#.+/(.+/.+/.+/.+)\$#\1#" )
            if check_sum "$file.Release" "${s}/${CHECKSUM_FILE}" "$file" ; then
                LANG=C gpgv $GPG_VERBOSITY --keyring $DEBIAN_KEYRING \
                     "$file.Release.gpg" "$file.Release" > "$dmp" 2>&1
                ret=$?
            else
                ret=3
            fi
        else
            echo "E: Could not download '${baseurl}/../../../../Release' and/or 'Release.gpg'." 1>&2
            ret=3
        fi
    fi

    $VERBOSE && cat "$dmp"
    grep "Good signature" "$dmp" | sed "s/gpgv/I/"
    [ -f "$dmp" ] && rm "$dmp"
    [ -f "$file.gpg" ] && rm $RM_VERBOSITY "$file.gpg"
    [ -f "$file.Release" ] && rm $RM_VERBOSITY "$file.Release"
    [ -f "$file.Release.gpg" ] && rm $RM_VERBOSITY "$file.Release.gpg"
    return $ret
}


# ------------------------------------------------------------ #
# fetch_files()
#       Download netboot image(s) and save them in the cache.
# Parameters: relase arch baseurl repo_loc tarfile
# Returns: (EXIT STATUS) 0=Success, 1=Error
# ------------------------------------------------------------ #
fetch_files() {
    local release="$1"		# Release (or variant)
    local arch="$2"			# Architecture
    local baseurl="$3"		# Download base URL
    local repo_loc="$4"		# Destination dir
    local tarfile="$5"		# File to download
    local getter file givenfile metadatafile success f
    local csum_file csum_found url cached fetch_date

    $DEBUG && set -x

    if ! $OFFLINE ; then
	if which wget > /dev/null ; then
	    getter="wget -c -x $WGET_VERBOSITY -O"
	elif which curl > /dev/null ; then
	    getter="curl --fail $CURL_VERBOSITY -o"
	else
	    echo "E: Can't download file. No download program (wget or curl) found." 1>&2
	    return 1
	fi
    fi

    metadatafile="$DL_CACHE/$(echo ${repo_loc}~~${release}--${arch}.meta | url2filename)"

    success=true
    csum_file=/dev/null
    for givenfile in $CHECKSUM_FILE $tarfile ; do
	f=$(echo $givenfile | sed -e 's#^\./##' -e 's#^/##' -e 's#//#/#g')
	url="$baseurl/$f"
	file="$DL_CACHE/$(echo $repo_loc/$f | url2filename)"
	[ -e $file.tmp ] && rm $file.tmp

	# Does the checksum of the previous file match the new one?
	cached=false
	csum_found=false
	if [ "$givenfile" = "$CHECKSUM_FILE" ]; then
	    csum_file="$file.tmp"
	    $OFFLINE && cp "$file" "$file.tmp"
	elif check_sum $csum_file $givenfile $file; then
	    cached=true
	    cp "$file" "$file.tmp"
	    echo "I: File $givenfile is already cached."
	else
	    [ -f "$file.tmp" ] && rm "$file.tmp"
	    $VERBOSE && ! $OFFLINE && echo "I: File '$givenfile' not cached, or obsolete."
	    $OFFLINE && success=false
	fi

	# Download the file, if needed.
	if ! $OFFLINE && ! $cached ; then
	    echo "I: Downloading '$givenfile'."
	    if $getter "$file.tmp" -- "$url" ; then
		if [ "$givenfile" = "$CHECKSUM_FILE" ] ; then
                    $VERBOSE && echo "Verify signature for '$url':"
                    if ! check_signature "$file.tmp" "$getter" "$baseurl" ; then
                        cat <<EOF

  * * * * * * * * * * * * WARNING * * * * * * * * * * * * *
  *                                                       *
  *  Could not verify/find the signature of the release.  *
  *                                                       *
  * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

EOF
                        read -e -n 1 -p "      Download and install the image anyway? [y|N]: " inp
                        inp=${inp:-N}
                        if [ "$inp" != "y" ] && [ "$inp" != "Y" ] ; then
                            echo -e "\nI: Image installation canceled."
                            success=false
                            break
                        else
                            echo "I: Continuing image installation."
                        fi
                    fi
                elif check_sum $csum_file $givenfile $file.tmp ; then
                    echo "I: Checksum verification succeeded for '$url'."
                    fetch_date="$(date -R)"
		else
                    echo "E: Checksum verification failed for '$url'." 1>&2
                    success=false
                    break
		fi
	    else
		echo "E: Can't download '$release' for '$arch' ($url)." 1>&2
		if [ -f "$file" ]; then
		    echo "I: You have a previous version in your cache (see --offline option)."
		fi
                success=false
                break
	    fi
	else
	    if [ ! -f "$file.tmp" ]; then
		success=false
		echo "E: Can't process '$release' in offline mode, the file is missing:" 1>&2
		echo "E: (expecting '$file' from '$url')" 1>&2
		break
	    else
		fetch_date="$( grep "^fetch_date=" "$metadatafile" | cut -d "=" -f 2- 2>/dev/null )"

		# Fall back, in case the file is manually added to the cache.
		[ -z "$fetch_date" ] && fetch_date="$(date -R --reference="$file.tmp" )"
	    fi
	fi
    done

    $VERBOSE && echo "I: Moving and/or removing temporary file(s):"
    for givenfile in $CHECKSUM_FILE $tarfile ; do
	f=$(echo $givenfile | sed -e 's#^\./##' -e 's#^/##' -e 's#//#/#g')
	file="$DL_CACHE/$(echo $repo_loc/$f | url2filename)"
	if $success ; then
	    [ -f "$file.tmp" ] && mv $MV_VERBOSITY "$file.tmp" "$file"
	else
	    [ -f "$file.tmp" ] && rm $RM_VERBOSITY "$file.tmp"
	fi
    done

    # Save metadata
    if $success ; then
	if ! $OFFLINE ; then
	    echo "#$PACKAGE_NAME for '$release' ($arch)" > $metadatafile
	    echo "format=1.0" >> $metadatafile
	    echo "fetch_date=$fetch_date" >> $metadatafile
	    echo "repo=$baseurl" >> $metadatafile
	    echo "dl_file=$tarfile" >> $metadatafile
	    echo "dist=$release" >> $metadatafile
	fi
    else
	return 1
    fi

    $DEBUG && set +x
    return 0
}


# ------------------------------------------------------------ #
# extract_files()
#	Extract (or copy) netboot image.
# Parameters: repo_loc file expand_dir
# Returns: (EXIT STATUS) 0=Success, 1=Error
# ------------------------------------------------------------ #
extract_files() {
    local repo_loc=$1		# Repository URL
    local file=$2			# Downloaded (tar) file
    local expand_dir=$3		# Target location fo extracted files
    local dl_file tar_opts

    file=$(echo $file | sed -e 's#^\./##' -e 's#^/##' -e 's#//#/#g')
    dl_file="$DL_CACHE/$(echo $repo_loc/$file | url2filename)"

    echo "I: Extracting '$dl_file'."

    if [ -d "$expand_dir" ]; then
	rm -Rf "$expand_dir/*"		# get existing metadata from the downloaded repository
    else
	mkdir -p "$expand_dir"
    fi
    :
    tar_opts="$dl_file --directory $expand_dir --no-same-permissions"
    case "$(echo "$dl_file" | sed -e 's#^.*/##' )" in
	*.tar.gz)
	    tar $TAR_VERBOSITY -zxf $tar_opts --strip-components 3 --exclude "./pxelinux.0" --exclude "./pxelinux.cfg"
	    tar $TAR_VERBOSITY -zxf $tar_opts ./version.info 2>/dev/null || true
	    ;;
	*.tar.bz2)
	    tar $TAR_VERBOSITY -jxf $tar_opts --strip-components 3 --exclude "./pxelinux.0" --exclude "./pxelinux.cfg"
	    tar $TAR_VERBOSITY -jxf $tar_opts ./version.info 2>/dev/null || true
	    ;;
	*.img)
	    cp $CP_VERBOSITY "$dl_file" "$expand_dir/$(basename "$file")"
	    ;;
	*)
	    echo "E: Don't know how to handle (unpack...) the file: $dl_file" 1>&2
	    return 1
	    ;;
    esac
    return 0
}


# ------------------------------------------------------------ #
# pxelinux_version()
#       Retrieve PXElinux version.
# Parameters: bin
# Returns: (STRING) PXElinux version
# ------------------------------------------------------------ #
pxelinux_version() {
    local bin="$1"		# pxelinux.0 file

    if [ -f "$bin" ]; then
	tr -c '[:print:] ' '\n' < $1 | sed -n -r "/PXELINUX [.0-9]+/ s/^[^ ]* ([0-9^.]+).*/\1/ p" | sort -r | head -n 1
    else
	echo ""
    fi
}


# ------------------------------------------------------------ #
# tweak_syslinux_arguments()
#       Tweak the kernel arguments in pxelinux configuration
#	files.
# Parameters: (PIPE) pristine configuration file
# Returns: (STRING) tweaked configuration file
# ------------------------------------------------------------ #
tweak_syslinux_arguments() {
    sed -e "/^[[:blank:]]*label[[:blank:]]\+install\$/I,/^\([[:blank:]]*label[[:blank:]]^+[^\(install\)]\|[[:blank:]]*\)\$/I{s!append \(.*\)--\(.*\)!append \1 $DI_ARGS -- \2 $TARGET_ARGS!}"
}


# ------------------------------------------------------------ #
# setup_syslinux()
#       Install and configure syslinux menu.
# Parameters: (PIPE) release arch metadatabasename expand_dir
# Returns: (EXIT STATUS) 0
# ------------------------------------------------------------ #
setup_syslinux() {
    local release=$1			# D-I image release name
    local arch=$2			# Architecture
    local metadatabasename=$3	# metadata location
    local expand_dir=$4		# Target installation dir.
    local pxelinuxbin pxelinuxcfg ver f fd dist title
    local menufragment menufragment_serial9600

    pxelinuxbin="$(find "$expand_dir" -type f -name "pxelinux.0" 2>/dev/null )"
    if [ ! "$pxelinuxbin" ]; then
	return
    fi
    pxelinuxcfg="${pxelinuxbin%%.0}.cfg/default"
    ver="$(sed -ne 's/# D-I config version \(.*\)/\1/p' "$pxelinuxcfg" 2>/dev/null)"
    if [ ! -f "$pxelinuxcfg" ] || echo "${ver:-1.0}" | grep -q -v "^[12]\.0" ; then
	echo "W: The format of this image may not be supported." 1>&2
    fi
    [ ! -d "$TFTP_ROOT/$N_A_DIR/pxelinux.cfg" ] && mkdir "$TFTP_ROOT/$N_A_DIR/pxelinux.cfg"

    copy_syslinux_bin "$SYSLINUX" "$TFTP_ROOT/$N_A_DIR/" || \
        copy_syslinux_bin "$expand_dir" "$TFTP_ROOT/$N_A_DIR/" || \
    	echo "E: No PXE binaries installed. Please file a bug." 1>&2

    # ensure only a single PXELINUX version is used for all its modules
    for f in $(find "$expand_dir" -type f -name '*.c32'); do
	case $(basename "$f") in
	    vesamenu.c32|menu.c32)
		cp -pft "$(dirname "$f")" "$TFTP_ROOT/$N_A_DIR/pxelinux.cfg/$(basename "$f")"
		;;
	    ldlinux.c32|libcom32.c32|libutil.c32)
		cp -pft "$(dirname "$f")" "$TFTP_ROOT/$N_A_DIR/$(basename "$f")"
		;;
	    *)
		echo "W: Unusual PXELINUX module \"$f\" may not work." 1>&2
		continue
		;;
	esac
    done

    for f in $(find "$expand_dir" -type f -a \( -name "default" -o -name "boot.txt" -o -name '*.cfg' \) ); do
	mv "$f" "$f.ORIG"
	print_do_not_edit_header "$f" > "$f"
        if [ "$(basename $f)" = "grub.cfg" ] ; then
	    sed -e "s#$REWRITEPKGPATH/$arch/#$N_A_DIR/$REPO_ALIAS/$arch/#" \
                "$f.ORIG" >> "$f"
        else
	    sed -e "s#$REWRITEPKGPATH/$arch/#::/$N_A_DIR/$REPO_ALIAS/$arch/#" \
                -e "s/^\([[:space:]]*default .*$\)/\#\1/" \
                -e "s/^\([[:space:]]*menu default[[:space:]]*$\)/\#\1/" \
                "$f.ORIG" | tweak_syslinux_arguments >> "$f"
        fi
    done
    menufragment="$metadatabasename.pxelinux.menu.fragment"
    menufragment_grub="$metadatabasename.grub.menu.fragment"
    menufragment_serial9600="$metadatabasename.pxelinux.menu.serial-9600.fragment"
    cat > $menufragment <<EOF
## This is a fragment of syslinux/pxelinux/grub menu file.
##
## DO NOT EDIT THIS FILE
##
## It is automatically generated by $PACKAGE_NAME
##
EOF
    cp $menufragment $menufragment_grub
    if [ "$(find $expand_dir -type d -name pxelinux.cfg.serial-9600 2>/dev/null)" ]; then
	cp $menufragment $menufragment_serial9600
    else
	[ -f "$menufragment_serial9600" ] && rm "$menufragment_serial9600"
    fi

    if $(grep "^repo=" ${metadatabasename}.conf | grep -q ubuntu) ; then
        dist="Ubuntu"
    else
        dist="Debian"
    fi
    fd=$(grep fetch_date ${metadatabasename}.conf | sed "s/fetch_date=//")
    fd=$(date --date="$fd" "+%Y%m%d  %R")
    title=$(printf "%-35s ${fd}\n" "$dist ${REPO_ALIAS} ($arch)")
    # Create top-menu fragment:
    cat >> $menufragment <<EOF
MENU BEGIN ${REPO_ALIAS}-$arch
        MENU TITLE $title
        LABEL ${REPO_ALIAS}-$arch
            MENU LABEL ^Back..
            MENU EXIT
        INCLUDE ::/$N_A_DIR/$REPO_ALIAS/$arch/boot-screens/menu.cfg
MENU END

EOF

    if [ "$dist" != "Ubuntu" ] ; then
        cat >> $menufragment_grub <<EOF
menuentry '$title' {
    configfile /$N_A_DIR/$REPO_ALIAS/$arch/grub/grub.cfg
}

EOF
    else
        cat >> $menufragment_grub <<EOF
menuentry '${title}' {
    linux /$N_A_DIR/$REPO_ALIAS/amd64/linux
    initrd /$N_A_DIR/$REPO_ALIAS/amd64/initrd.gz
}

EOF
    fi

    if [ -f "$menufragment_serial9600" ]; then
	(
	    echo   "LABEL ${REPO_ALIAS}-$arch"
	    printf "	MENU LABEL Debian Installer %-26s [SUB-MENU]\n" "($REPO_ALIAS, $arch)"
	    echo   "	KERNEL ::/$N_A_DIR/pxelinux.cfg/menu.c32"
	    echo   "	APPEND ::/$N_A_DIR/$REPO_ALIAS/$arch/pxelinux.cfg.serial-9600/default"
	) >> $menufragment_serial9600
    fi
    return 0
}


# ------------------------------------------------------------ #
# install_repo_for_arch()
#	Extract/copy the downloaded file for given arch.
# Parameters: arch release
# Returns: (EXIT STATUS) 0=Success, 1=Error
# ------------------------------------------------------------ #
install_repo_for_arch() {
    local arch=$1			# Architecture
    local release=$2		# D-I image release name
    local reg metadatabasename file fetch_date
    local metadatafile repo_orig repo_mirror expand_dir

    echo "I: Processing $release/$arch."
    $DEBUG && set -x

    metadatabasename="$STATUS_LIB/${REPO_ALIAS}--${arch}"
    metadatafile="$metadatabasename.conf"

    repo_orig="$(grep -E "^$release[[:blank:]]$arch\>" "$DISOURCELIST")"
    repo_mirror="$repo_orig"
    for reg in $MIRROR_REGEXPS "s=/$==" ; do
	repo_mirror="$(echo "$repo_mirror" | sed -e "$reg")"
    done

    repo="$(echo "$repo_orig" | cut -f 3 | sed -e 's#\([^:]/\)/#\1#g' -e 's#/$##' )"
    repo_mirror="$(echo "$repo_mirror" | cut -f 3 | sed -e 's#\([^:]/\)/#\1#g' -e 's#/$##' )"

    if [ -z "$repo" ]; then
	echo "E: There is no entry declared for architecture '$arch' for repository '$release' in $DISOURCELIST" 1>&2
	list_declared_arch_for_repo "$release"
	return 1
    fi

    if [ "$release" != "$REPO_ALIAS" ] ; then
	echo "I: Repository '$release' filed as '$REPO_ALIAS'."
    fi

    repo_loc="$(echo $repo | sed -e 's#^[^:]\+://##')"
    expand_dir="$TFTP_ROOT/$N_A_DIR/$REPO_ALIAS/$arch"

    file=$(grep -E "^$release[[:blank:]]$arch" "$DISOURCELIST" | cut -f 4- )
    fetch_date=""
    if ! fetch_files "$release" "$arch" "$repo_mirror" "$repo_loc" $file; then
	return 1
    fi
    if ! extract_files "$repo_loc" "$file" "$expand_dir"; then
	return 1
    fi

    # save metadata of this repository
    grep -v -E "^format=.*" \
	 "$DL_CACHE/$(echo ${repo_loc}~~${release}--${arch}.meta | url2filename)" \
	| sed -e "s/^fetch_date=/format=1.0\n\0/" \
	      > $metadatafile
    echo "expand_dir=$expand_dir" >> $metadatafile
    echo "di_args=$DI_ARGS" >> $metadatafile
    echo "target_args=$TARGET_ARGS" >> $metadatafile


    # PXELINUX MENUs (i386, amd64)
    setup_syslinux "$release" "$arch" "$metadatabasename" "$expand_dir"

    $DEBUG && set +x
    return 0
}


# ------------------------------------------------------------ #
# install_repo_for_archs()
#	Extract/copy the downloaded file for specified archs.
# Parameters: release
# Returns: (EXIT STATUS) 0=Success, 1=Error
# ------------------------------------------------------------ #
install_repo_for_archs() {
    local release="$1"		# release name to install
    local archs ret

    if [ -z "$release" ]; then
	echo "E: No repository specified (valid repositories are: ${releases})." 1>&2
	return 1
    fi

    if ! grep -Eq "^$release\>" "$DISOURCELIST" ; then
	echo "E: Invalid repository name specified ($release)." 1>&2
	echo -e "E: Declared repositories are:\n${releases}" 1>&2
	return 1
    fi

    [ ! "$ARCH" ] && ARCH=$DEFAULT_ARCH

    if [ "$(echo $ARCH | grep -E "\<all\>")" ]; then
	archs="$(get_declared_arch_for_repo $release)"
    else
	archs="$(echo $ARCH | tr ',' ' ')"
    fi


    if [ -z "$( echo "$archs" | sed -e 's/[[:blank:]\?]*//g' )" ]; then
	echo "E: No architecture specified." 1>&2
	list_declared_arch_for_repo "$release"
	return 1
    fi

    for arch in $archs ; do
        install_repo_for_arch "$arch" "$release"
        ret=$?
        if [ $ret != 0 ] ; then
            return $ret
        fi
    done
    return 0
}


# ---------------------------  Main  ------------------------- #

ACTION=
COUNT=0

for option in "$@"; do
    case "$option" in
        -h | --help)
	    usage
	    exit 0 ;;
        -V | --version)
	    echo "$PACKAGE_NAME $PACKAGE_VERSION"
	    exit 0 ;;
        --arch| --arch=*)
	    ARCH="$(echo $option | sed -e 's/--arch[=]\?//' -e 's/,,/,/' -e 's/^,\+//' -e 's/,\+$//' )"
	    #Note:
	    if [ "$(echo $ARCH | grep -E '^[[:alnum:]_,]\+$')" ]; then
		echo "E: Invalid architecture specified ($ARCH)" 1>&2
		exit 1
	    fi ;;
        --alias| --alias=*)
	    CLI_ALIAS="$(echo $option | sed -e 's/--alias[=]\?//' | grep -E "^[[:alnum:]_-]+$" )"
	    #Note:
	    if [ "$(echo $CLI_ALIAS | grep -E '^[[:alnum:]_,]\+$')" ]; then
		echo "E: Invalid alias name ($option)" 1>&2
		exit 1
	    fi ;;
        --di-args=*)
	    DI_ARGS="$DI_ARGS ${option#--di-args=}"
	    ;;
        --target-args=*)
	    TARGET_ARGS="$TARGET_ARGS ${option#--target-args=}"
	    ;;
        --offline)
	    OFFLINE=true ;;
        -v | --verbose)
	    VERBOSE=true
	    ;;
        --debug)
	    # This is an undocumented feature...
	    DEBUG=true ;;
        --di-args| --target-args)
	    echo "E: Option $option requires a value after equal sign." 1>&2
	    exit 1
            ;;
        -*)
	    echo "E: Unrecognized option ($option)" 1>&2
	    exit 1
	    ;;
        rebuild-menu|install|uninstall|uncache|purge)
	    #Actions are processed in the loop below
    	    if [ "$ACTION" ]; then
		echo "E: Unexpected command '$option'. '$ACTION' was already specified." 1>&2
		exit 1
	    fi
	    ACTION=$option
	    ;;
        *)
	    COUNT=$(( $COUNT + 1 ))
	    ;;
    esac
done

if $VERBOSE; then
    WGET_VERBOSITY=""
    CURL_VERBOSITY=""
    RM_VERBOSITY="${RM_VERBOSITY:=-v}"
    MV_VERBOSITY="${MV_VERBOSITY:=-v}"
    CP_VERBOSITY="${CP_VERBOSITY:=-v}"
    TAR_VERBOSITY="${TAR_VERBOSITY:=-v}"
    GPG_VERBOSITY="${GPG_VERBOSITY:=-v}"
else
    WGET_VERBOSITY="--quiet"
    CURL_VERBOSITY="--silent"
    RM_VERBOSITY="${RM_VERBOSITY:=}"
    MV_VERBOSITY="${MV_VERBOSITY:=}"
    CP_VERBOSITY="${CP_VERBOSITY:=}"
    TAR_VERBOSITY="${TAR_VERBOSITY:=}"
    GPG_VERBOSITY="${GPG_VERBOSITY:=-q}"
fi

DEFAULT_ARCH="$(detect_current_arch)"

if ! check_di_source_list; then
    exit $?
fi

releases="$(grep -vE '^#' "$DISOURCELIST" | cut -f 1 | sort -u | tr "\n" " " |\
                  sed -e 's/^[[:blank:]]\+//' -e 's/[[:blank:]]\+$//')"

if [ -n "$CLI_ALIAS" ]; then
    if [ "$ACTION" = "install" -a $COUNT -gt 1 ]; then
        echo "E: Option --alias can't be used with multiple repositories." 1>&2
        exit 1
    fi
fi

case "$ACTION" in
    '')
    #Skip, if no action specified
    	;;
    rebuild-menu|rebuild-grub)
        if [ $COUNT -ne 0 ]; then
            echo "E: Unexpected argument after command '$ACTION'." 1>&2
            exit 1
	fi
        ;;
    *)
        if [ $COUNT -eq 0 ]; then
            echo "E: No repository name was passed for '$ACTION'." 1>&2
            ! $OFFLINE && [ "$ACTION" = "install" ] && echo "I: Declared repositories are:" &&\
                echo "${releases}"
            cached_repos="$( find $DL_CACHE -name "*~~*--*.meta" | \
                             sed -e 's/^.*~~//' -e 's/--.*\.meta//' | sort -u | tr "\n" " " | \
                             sed -e 's/[[:blank:]]\+$//')"
            installed_repos="$(get_installed_repos)"
            purgabled_repos="$(echo $cached_repos $installed_repos | tr " " "\n" | sort -u | \
            tr "\n" " " | sed -e 's/[[:blank:]]\+$//')"
            [ "$ACTION" = "uncache" ] && echo -e "I: Cached repositories are:\n${cached_repos}"
            [ "$ACTION" = "uninstall" ] && echo -e "I: Installed repositories are:\n${installed_repos}"
            [ "$ACTION" = "purge" ] && echo -e "I: Purgable repositories are:\n${purgabled_repos}"
            exit 1
        fi
        ;;
esac


ACTION=
COUNT=0
for option in "$@"; do
    case "$option" in
        -*)
    	# Ignore options on this pass
    	;;
        install|uninstall|uncache|purge)
	    ACTION=$option
	    ;;
        rebuild-menu)
	    ACTION=$option
            update_menu
            ;;
        rebuild-grub)
	    ACTION=$option
            prepare_grub "$option"
            ;;
        *)
            if [ -n "$CLI_ALIAS" ] ; then
                REPO_ALIAS="$CLI_ALIAS"
            else
                REPO_ALIAS="$option"
            fi
	    case "$ACTION" in
		install)
		    check_tftp_root
		    cd "$TFTP_ROOT/$N_A_DIR"
	            if install_repo_for_archs "$option" ; then
                        update_menu
		    else
                        rmdir --ignore-fail-on-non-empty "$TFTP_ROOT/$N_A_DIR"
                        exit 1
		    fi
		    ;;
		uninstall)
		    uninstall_repos "$option" ""
		    update_menu
		    ;;
		uncache)
		    remove_repocaches "$option" ""
		    ;;
		purge)
		    uninstall_repos "$option" "ignore_missing"
		    update_menu
		    remove_repocaches "$option" "ignore_missing"
		    ;;
		rebuild-menu|rebuild-grub)
		    echo "W: Argument '$option' ignored ($ACTION expects no argument)." 1>&2
		    ;;
		*)
		    echo "E: Unexpected keyword: '$option'. No action were specified." 1>&2
		    exit 1
		    ;;
	    esac
	    COUNT=$(( $COUNT + 1 ))
    esac
done

if [ ! "$ACTION" ]; then
    usage 1>&2
    exit 1
fi
