#!/bin/bash

# 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 ------------------- #
TRUE="true"
FALSE= #false#
PACKAGE_NAME=di-netboot-assistant
PACKAGE_VERSION=0.39

# -------------- Initialize the global variables ------------- #
OFFLINE=$FALSE
DEBUG=$FALSE
DISOURCELIST=/etc/di-netboot-assistant/di-sources.list
SYSLINUX=/usr/lib/syslinux/
ELILO=/usr/lib/elilo/
DL_CACHE=/var/cache/di-netboot-assistant
STATUS_LIB=/var/lib/di-netboot-assistant
TEMPLATES=/etc/di-netboot-assistant
TFTP_ROOT=/var/lib/tftpboot
DI_PKG_DIR=di-netboot-pkg  # where the debian-installer-*-netboot-* image is copied or bind mounted to
REPO_ALIAS=""
REWRITEPKGPATH='\(debian\|ubuntu\)-installer'
EXTRA_DL_FILES="MD5SUMS"
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

VERBOSE=$FALSE
WGET_VERBOSITY="--quiet"
CURL_VERBOSITY="--silent"
RM_VERBOSITY=
MV_VERBOSITY=
CP_VERBOSITY=
TAR_VERBOSITY=

# ------------------- 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.

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
}

# ------------------------------------------------------------ #
# 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 using templates"
    echo "## from $templatename "
    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
}

# ------------------------------------------------------------ #
# 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/debian-installer/" = "$dst" ]; then
	for f in ldlinux.c32 libcom32.c32 libutil.c32; do
	    srcf="$(find_file $f "$src")"
	    [ -z "$srcf" ] || cp -np "$srcf" "$TFTP_ROOT/debian-installer/$f"
	done
    fi
    return 0
}

# ------------------------------------------------------------ #
# copy_elilo_bin()
#	Install elilo binaries in the target folder.
# Parameters: src dst
# Returns: (EXIT STATUS) 0=Success, 1=Error
# ------------------------------------------------------------ #
copy_elilo_bin() {
    local src=$1			# Source directory
    local dst=$2			# Target directory
    local f srcf newbin elilo_new_ver elilo_cur_ver

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

    newbin=$(find_file elilo.efi "$src" 2>/dev/null)
    [ ! -f "$dst/elilo.efi" -a ! -f "$newbin" ] && return 1

    elilo_new_ver="$(elilo_version "$newbin")"
    elilo_cur_ver="$(elilo_version "$dst/elilo.efi")"
    if version_lte "$elilo_new_ver" "$elilo_cur_ver"; then
	return 0
    fi

    echo "I: Upgrading elilo ($elilo_cur_ver to $elilo_new_ver)"

    for f in elilo.efi; do
	srcf="$(find_file $f "$src")"
	if [ -f "$srcf" ]; then
	    cp -p "$srcf" "$dst/$f"
	else
	    [ -f "$dst/$f" ] && rm "$dst/$f"
	fi
    done

    return 0
}

# ------------------------------------------------------------ #
# update_menu()
#	Create the bootloaders top menu.
# Parameters: (NONE)
# Returns: (NULL)
# ------------------------------------------------------------ #
update_menu() {
    if [ ! -d "$TFTP_ROOT/debian-installer" ]; then
	return
    fi
    cd "$TFTP_ROOT/debian-installer"

    update_pxelinux_menu
    include_installer_packages
    update_elilo_menu

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

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

# ------------------------------------------------------------ #
# update_pxelinux_menu()
#	Create PXElinux bootloader top menu.
# Parameters: (NONE)
# Returns: (NULL)
# ------------------------------------------------------------ #
update_pxelinux_menu() {
    local x i

    [ ! -d "pxelinux.cfg" ] && mkdir "pxelinux.cfg"
    [ "$VERBOSE" ] && echo "I: Building PXE-Linux' top-menu"
    print_do_not_edit_header "$TEMPLATES/pxelinux.HEAD" > pxelinux.cfg/default
    if [ -n "$(find "$TFTP_ROOT/debian-installer" -type d -name pxelinux.cfg.serial-9600 2>/dev/null)" ]; then
	cp 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

    i=0
    for x in $(ls "$STATUS_LIB/"*.pxelinux.menu.fragment 2>/dev/null ); do
	i=$(($i + 1))
	grep -Ev "^##" $x >> pxelinux.cfg/default
	echo "" >> pxelinux.cfg/default
    done
    [ $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
    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
}

# ------------------------------------------------------------ #
# include_installer_packages()
#	Create PXElinux bootloader menu for installed debian-installer-*-netboot-* packages.
# Parameters: (NONE)
# Returns: (NULL)
# ------------------------------------------------------------ #
include_installer_packages() {
    local x title relpath
    if [ ! -e "$TFTP_ROOT/debian-installer/pxelinux.0" ] ; then
        copy_syslinux_bin "$SYSLINUX" "$TFTP_ROOT/debian-installer/" || \
            copy_syslinux_bin "${TFTP_ROOT}/${DI_PKG_DIR}" "$TFTP_ROOT/debian-installer/" || \
            echo "E: No PXE binaries found and installed."
    fi
    [ "$VERBOSE" ] && echo "I: Building menu entries for debian-installer-*-netboot-* packages."
    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: ##")\
$(echo $relpath | sed -e "s#$DI_PKG_DIR/images/# #")"
	cat >> pxelinux.cfg/default <<EOF
LABEL $title
        MENU LABEL $title
        CONFIG ::${relpath}/pxelinux.cfg/default ::${relpath}/

EOF
        [ "$VERBOSE" ] && echo "I: Added:  ${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
	    [ ! -f ../$x ] && echo "W: The binary '${TFTP_ROOT}/$x' mentioned in the PXE boot menu is missing."
	done
    else
	find pxelinux.cfg/ -iregex '.*\(\.c32\|\.bak.*\|~\)$' | xargs -r rm
	[ -d pxelinux.cfg ] && rmdir --ignore-fail-on-non-empty pxelinux.cfg
	[ ! -d pxelinux.cfg -a -e pxelinux.0 ] && rm pxelinux.0
    fi
}

# ------------------------------------------------------------ #
# update_elilo_menu()
#	Create Elilo bootloader top menu.
# Parameters: (NONE)
# Returns: (NULL)
# ------------------------------------------------------------ #
update_elilo_menu() {
    local x i

    [ "$VERBOSE" ] && echo "I: Building Elilo's top-menu"
    print_do_not_edit_header "$TEMPLATES/elilo.HEAD" > elilo.conf
    [ -f $TEMPLATES/elilo.HEAD ] && grep -Ev "^##" "$TEMPLATES/elilo.HEAD" >> elilo.conf

    i=0
    for x in $(ls "$STATUS_LIB/"*.elilo.conf.fragment 2>/dev/null ); do
	i=$(($i + 1))
	grep -Ev "^##" $x >> elilo.conf
	echo "" >> elilo.conf
    done

    if [ $i -eq 0 ]; then
	rm elilo.conf
	[ -f elilo.efi ] && rm elilo.efi
    fi
}

# ------------------------------------------------------------ #
# 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/debian-installer" ] && mkdir "$TFTP_ROOT/debian-installer"
    if [ ! -w "$TFTP_ROOT/debian-installer" ]; then
	echo "E: Can't write to DI directory ($TFTP_ROOT/debian-installer)" 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 --ignore-fail-on-non-empty "$dist_dir"

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

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

    s="$metadatabasename.elilo.conf.fragment"
    [ -f "$s" ] && rm "$s"

    rm "$dist_conf"
}

# ------------------------------------------------------------ #
# 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 "I: (installed repositories are: $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 "I: (installed arch are: $(echo $installed_archs | tr "\n" " "))"
		return 1
	    fi
	fi
    done

    [ "$DEBUG" ] && set +x
}


#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 $metadatafile

}

# ------------------------------------------------------------ #
# 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
}

# ------------------------------------------------------------ #
# check_sum()
#       Validate a file's checksum
# Parameters: csum_bin csum_file fname actual_file
# Returns: (EXIT STATUS) 0=checksum is ok, 1=checksum mismatch
# ------------------------------------------------------------ #
check_sum() {
    local csum_bin=$1		# checksum program
    local csum_file=$2		# file containing checksum
    local fname=$3			# file to check
    local actual_file=$4		# original filename
    local sum

    if [ ! -f "$actual_file" -o ! -f "$csum_file" ]; then
	return 1
    fi
    sum=$($csum_bin  $actual_file | cut -d " " -f 1)
    if ! grep -qiE "^$sum[[:blank:]]+(\./|)$fname[[:blank:]]*$" $csum_file ; then
	return 1
    fi
    return 0
}

# ------------------------------------------------------------ #
# fetch_files()
#       Download netboot image, 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 file givenfile metadatafile succeed getter
    local f csum_bin csum_file skip_csum url cached
    local 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 tool detected (wget or curl)." 1>&2
	    return 1
	fi
    fi

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

    succeed=$TRUE
    csum_bin=
    csum_file=/dev/null
    for givenfile in $EXTRA_DL_FILES $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
	skip_csum=$FALSE
	if [ "$givenfile" = "MD5SUMS" ]; then
	    csum_bin=md5sum
	    csum_file="$file.tmp"
	    [ "$OFFLINE" ] && cp "$file" "$file.tmp"
	    skip_csum=true
	elif [ "$csum_bin" ]; then
	    if check_sum $csum_bin $csum_file $givenfile $file; then
		cached=true
		cp "$file" "$file.tmp"
		[ "$VERBOSE" ] && echo "I: File $givenfile is already cached"
	    else
		[ -f "$file.tmp" ] && rm "$file.tmp"
		[ "$VERBOSE" -a "$OFFLINE" ] && echo "I: $givenfile not cached, or obsolete."
		[ "$OFFLINE" ] && succeed=$FALSE
	    fi
	fi

	# Download the file, if needed.
	if [ ! "$OFFLINE" -a ! "$cached" ]; then
	    [ 0$VERBOSE -eq 1 ] && echo "I: Downloading $givenfile"
	    if $getter "$file.tmp" -- "$url" ; then
		if check_sum $csum_bin $csum_file $givenfile $file.tmp || [ "$skip_csum" ] ; then
		    fetch_date="$(date -R)"
		else
		    echo "E: Checksum verification failed (file $givenfile)." 1>&2
		    break
		fi
	    else
		echo "$EXTRA_DL_FILES" | tr " " "\n" | grep -qx $f || succeed=$FALSE
		echo "E: Can't download '$release' for '$arch' ($url)." 1>&2
		[ -f "$file.tmp" ] && rm $RM_VERBOSITY "$file.tmp"
		if [ -f "$file" ]; then
		    echo "I: You have a previous version in your cache (see --offline option)."
		fi
	    fi
	else
	    if [ ! -f "$file.tmp" ]; then
		succeed=$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

    if [ ! "$csum_bin" ]; then
	echo "W: No checksum found."
    fi

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

    # Save metadata
    if [ "$succeed" ]; 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

	return 0
    else
	return 1
    fi
    [ "$DEBUG" ] && set +x
}

# ------------------------------------------------------------ #
# 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)"

    [ "$VERBOSE" ] && echo "I: Extracting... "

    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
}

# ------------------------------------------------------------ #
# elilo_version()
#       Retrieve Elilo version (or use timestamp as version)
# Parameters: bin
# Returns: (STRING) elilo version
# ------------------------------------------------------------ #
elilo_version() {
    local bin="$1"		# elilo.efi file

    if [ -f "$bin" ]; then
	date --reference=$bin +0.%Y-%m-%d.%H-%M-%S
    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!}"
}

# ------------------------------------------------------------ #
# tweak_elilo_arguments()
#       Tweak the kernel arguments in pxelinux configuration
#	files.
# Parameters: (PIPE) pristine configuration file
# Returns: (STRING) tweaked configuration file
# ------------------------------------------------------------ #
tweak_elilo_arguments() {
    sed -e "/^[[:blank:]]*label=install\$/,/^\([[:blank:]]*label=[^\(install\)]\|[[:blank:]]*\)\$/{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
    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/debian-installer/pxelinux.cfg" ] && mkdir "$TFTP_ROOT/debian-installer/pxelinux.cfg"

    copy_syslinux_bin "$SYSLINUX" "$TFTP_ROOT/debian-installer/" || \
        copy_syslinux_bin "$expand_dir" "$TFTP_ROOT/debian-installer/" || \
    	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/debian-installer/pxelinux.cfg/$(basename "$f")"
		;;
	    ldlinux.c32|libcom32.c32|libutil.c32)
		cp -pft "$(dirname "$f")" "$TFTP_ROOT/debian-installer/$(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 "$release netboot image" > "$f"
	sed -e "s#$REWRITEPKGPATH/$arch/#::/debian-installer/$REPO_ALIAS/$arch/#" "$f.ORIG" \
	    | tweak_syslinux_arguments >> "$f"
    done
    menufragment="$metadatabasename.pxelinux.menu.fragment"
    menufragment_serial9600="$metadatabasename.pxelinux.menu.serial-9600.fragment"
    echo "## This is a fragment of syslinux/pxelinux menu file." > $menufragment
    echo "##" >> $menufragment
    echo "## DO NOT EDIT THIS FILE" >> $menufragment
    echo "##" >> $menufragment
    echo "## It is automatically generated by $PACKAGE_NAME" >> $menufragment
    echo "##" >> $menufragment

    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

    # Create top-menu fragment (use the most appropriate **menu.c32)
    echo  "LABEL ${REPO_ALIAS}-$arch" >> $menufragment
    printf "	MENU LABEL Debian Installer %-26s [SUB-MENU]\n" "($REPO_ALIAS, $arch)" >> $menufragment

    if [ "$(grep -Eiq "^[[:blank:]]*prompt[[:blank:]]+0" "$expand_dir/pxelinux.cfg/default" && grep -Eiq "^[[:blank:]]*default[[:blank:]].*/vesamenu.c32" "$expand_dir/pxelinux.cfg/default" && echo ok)" ]; then

	echo -n  "	KERNEL " >> $menufragment
	grep -Ei "^[[:blank:]]*default[[:blank:]].*/vesamenu.c32" \
	     $expand_dir/pxelinux.cfg/default \
	    | sed -e 's/^[[:blank:]]*default[[:blank:]]//' \
		  >> $menufragment

	echo -n  "	APPEND " >> $menufragment
	if grep -qEi "^[[:blank:]]*include[[:blank:]]" $expand_dir/pxelinux.cfg/default ; then
	    grep -Ei "^[[:blank:]]*include[[:blank:]]" \
		 $expand_dir/pxelinux.cfg/default \
		| sed -e 's/^[[:blank:]]*include[[:blank:]]//' \
		      >> $menufragment
	else
	    echo   "	APPEND debian-installer/$REPO_ALIAS/$arch/pxelinux.cfg/default" >> $menufragment
	fi

    else
	echo   "	KERNEL ::/debian-installer/pxelinux.cfg/menu.c32" >> $menufragment
	echo   "	APPEND ::/debian-installer/$REPO_ALIAS/$arch/pxelinux.cfg/default" >> $menufragment
    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 ::/debian-installer/pxelinux.cfg/menu.c32"
	    echo   "	APPEND ::/debian-installer/$REPO_ALIAS/$arch/pxelinux.cfg.serial-9600/default"
	) >> $menufragment_serial9600
    fi
}

# ------------------------------------------------------------ #
# setup_elilo()
#       Install and configure elilo menu.
# Parameters: (PIPE) release arch metadatabasename expand_dir
# Returns: (EXIT STATUS) 0
# ------------------------------------------------------------ #
setup_elilo() {
    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 f menufragment

    if [ ! "$(find "$expand_dir" -type f -name 'elilo.conf' )" ]; then
	return
    fi

    #Install elilo from locally installed package (if any)
    copy_elilo_bin "$ELILO" "$TFTP_ROOT/debian-installer/" || false

    #Install/Upgrade elilo with package version included in the tarball.
    if ! copy_elilo_bin "$expand_dir" "$TFTP_ROOT/debian-installer/" ; then
	echo "E: No Elilo menu installed. Please file a bug." 1>&2
    fi

    #Rewrite existing menus
    for f in $(find "$expand_dir" -type f -name 'elilo.conf' ); do
	mv "$f" "$f.ORIG"
	print_do_not_edit_header "release" > "$f"
	sed -e "s#^\([[:blank:]]*\(image\|initrd\|message\|f[0-9]\+\)=\)#\1debian-installer/$REPO_ALIAS/$arch/#" < "$f.ORIG" >> "$f"
    done

    #Create overview menu.
    menufragment="$metadatabasename.elilo.conf.fragment"
    echo "## This is a fragment of elilo menu file." > $menufragment
    echo "##" >> $menufragment
    echo "## DO NOT EDIT THIS FILE" >> $menufragment
    echo "##" >> $menufragment
    echo "## It is automatically generated by $PACKAGE_NAME" >> $menufragment
    echo "##" >> $menufragment

    echo "# ### $release ($REPO_ALIAS) $arch ### " >> $menufragment
    awk '/^image=/,/^$/' "$expand_dir/elilo.conf" \
	| tweak_elilo_arguments \
	| sed -e "s/^[[:blank:]]*label=.*$/\0-${REPO_ALIAS}-$arch/" \
	| sed -e "s/^\([[:blank:]]*description\)=\"\([^\"]\+\)\"/\1=\"\2 ($release $arch)\"/" \
	      >> $menufragment
    echo "" >> $menufragment
}

# ------------------------------------------------------------ #
# install_repo_for_arch()
#	Extract/Copy the downloaded file a 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 " 1>&2
	echo "E: repository '$release' in $DISOURCELIST" 1>&2
	list_declared_arch_for_repo "$release"
	return 1
    fi

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

    repo_loc="$(echo $repo | sed -e 's#^[^:]\+://##')"
    expand_dir="$TFTP_ROOT/debian-installer/$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"

    # ELILO MENU (ia64, and some ia32)
    setup_elilo "$release" "$arch" "$metadatabasename" "$expand_dir"

    return 0
    [ "$DEBUG" ] && set +x
}

# ------------------------------------------------------------ #
# 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

    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 "I: Declared repositories are: $releases"
	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
	if ! install_repo_for_arch $arch $release; then
	    return $?
	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=*)
	    REPO_ALIAS="$(echo $option | sed -e 's/--alias[=]\?//' | grep -E "^[[:alnum:]_-]+$" )"
	    #Note:
	    if [ "$(echo $REPO_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=$(( 0$VERBOSE + 1 ))
	    if [ $VERBOSE -gt 1 ]; then
		WGET_VERBOSITY=""
		CURL_VERBOSITY=""
		RM_VERBOSITY="-v"
		MV_VERBOSITY="-v"
		CP_VERBOSITY="-v"
		TAR_VERBOSITY="-v"
	    fi
	    ;;
        --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

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:]]\+//' )"

if [ "$REPO_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)
        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" -a "$ACTION" = "install" ] && echo "I: Declared repositories are: $releases"
	    cached_repos="$( find $DL_CACHE -name "*~~*--*.meta" | sed -e 's/^.*~~//' -e 's/--.*\.meta//' | sort -u | tr "\n" " " )"
	    installed_repos="$(get_installed_repos)"
	    purgabled_repos="$(echo $cached_repos $installed_repos | tr " " "\n" | sort -u | tr "\n" " ")"
            [ "$ACTION" = "uncache" ] && echo "I: Cached repositories are: $cached_repos"
            [ "$ACTION" = "uninstall" ] && echo "I: Installed repositories are: $installed_repos"
            [ "$ACTION" = "purge" ] && echo "I: Purgable repositories are: $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
            ;;
        *)
	    [ ! "$REPO_ALIAS" ] && REPO_ALIAS=$option
	    case "$ACTION" in
		install)
		    check_tftp_root
		    cd "$TFTP_ROOT/debian-installer"
                    cat <<EOF
For security reasons, it is recommended to use the installer packages debian-installer-*-netboot-*.
They are available in the package archives.  Consult the README available in
'/usr/share/doc/di-netboot-assistant/' for further information.

EOF
                    read -e -n 1 -p "Exit now and skip downloading images insecurely?  [Y|n]: " inp
                    inp=${inp:-Y}
                    if [ "$inp" = "y" ] || [ "$inp" = "Y" ] ; then
                        exit 0
                    fi
		    if install_repo_for_archs "$option" ; then
			update_menu
		    else
			rmdir --ignore-fail-on-non-empty "$TFTP_ROOT/debian-installer"
		    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)
		    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
