#!/bin/sh -efu
#
# Copyright (C) 2020  BaseALT /basealt.ru/
#
# 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, Fifth Floor, Boston, MA 02110-1301, USA.
#

PROG="${0##*/}"
PROG_VERSION=0.1.4

MOUNTPOINTS="/proc,/dev/pts"
CONPONENTS="classic"
DEFARCH="$(uname -m)"

. shell-quote

show_help()
{
	cat <<EOF
Usage: $PROG [options] [skiplist|skipdir|NVR|*.tar ...]

$PROG is a tool to remove unused source files from SRPM packages.
The list of unused files can be obtained with hsh-separate-sources(1).

Main options:

  -T TDIR, --tardir=TDIR    output the intermediate TAR archives to
                          directory TDIR;

  -t, --tar-only          do not generate SRPM from TAR;

  -O ODIR, --outdir=ODIR    output the resulting SRPM files to
                          directory ODIR;

  -l LDIR, --logdir=LOGDIR    save Hasher logs into LOGDIR;

  -x XLIST, --exceptions=XLIST    don't truncate files matching the
                                patterns listed in XLIST;

  -N NVR, --nvr=NVR       name of the SRPM package (stdin and single
                          skiplist modes only);

  -s SKIPDIR, --skipdir=SKIPDIR    search SKIPDIR for skiplists of the
                                 packages given on the command line;

  --translate=TSCRIPTDIR    search TSCRIPTDIR for list translation
                          scripts;

  -E, --abort-on-error    abort on first error;

  -f, --force             overwrite existing results;

  --limit-LIMIT           limit the work plan by LIMIT packages;

  -q, --quiet             do not print any info while running;

  -v, --verbose           print more info while running;

  --dry-run[-run]         calculate the number of packages to clean
                          and exit; in multi-mode show GNU Parallel's
                          dry run preview;

  --check-args            exit after checking the arguments;

  -V,--version            print program version and exit;

  -h,--help               show this text and exit.

Main options, related to GNU Parallel:

  --with-srpms[=FROM]     transfer original SRPMs (from directory
                          FROM) to remote cleaners;

  --with-tars             transfer package tarballs from directory
                          TDIR to remote cleaners;

  --selfie                copy this script to remote cleaners;

  --remote-dir=DIR        working home on the remote cleaners;

  --cleanup               clean-up SRPMs, self and resulting lists
                          on remote cleaners;

  --joblog=JOBLOG         keep job log JOBLOG in order to --retry
                          and --resume;

  --resume                resume from the last unfinished job
                          (requires --joblog);

  --resume-failed         retry all failed and resume from the last
                          unfinished job (requires --joblog);

  --retry-failed          retry all failed jobs (requires --joblog);

  --retries=N             try each failed job N times;

  --controlmaster         use ssh's ControlMaster option;

  --sshdelay=SECS         delay starting next remote job by SECS
                          seconds (which can be a fraction of 1 s);

  --parallel-opts=OPTS    pass the specified options to GNU Parallel;

  --rsync-opts=OPTS       additional options for rsync.

Cleaner configuration options:

  -C CONF, --apt-config=CONF    use --apt-config=CONF with Hasher;

  -R ADDR, --repo=ADDR      alternatively, configure Hasher to use
                            the repository at ADDR;

  -B DIR, --base=DIR        SRPM base directory;

  -n NUMS, --nums=NUMS      allowed Hasher slot (subconfig) numbers;
                            it also defines the number of gear
                            processes for preparing TAR files;

  -d DIR, --dir=DIR         Hasher working directory path;

  -m LIST, --mountpoints=LIST    use the given (possibly empty)
                               mountpoint list instead of the
                            default: $MOUNTPOINTS;

  --debug                   do not delete temporary directory
                            on exit; implies -v|--verbose;

  --id=NAME                 cleaner name to use in log;

  -a ARCHES, --arch=ARCHES    architectures this host supports (comma
                            and space serarated);

  -e SSH, --ssh=SSH         use SSH remote shell to reach remote
                            cleaners (default is ssh);

  -u USER, --user=USER      name of a remote user to login with;

  -H REMOTES, --host=REMOTES    add remote cleaners and make any of
                              the above cleaner configuration options
                            specified after it specific to that
                            remotes.


For more information see srpm-cleanup(1).
Report bugs to https://bugzilla.altlinux.org/.

EOF
}

print_version()
{
	cat <<EOF
$PROG version $PROG_VERSION
Written by: see the source for author info.

Copyright (C) 2020 BaseALT /basealt.ru/
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.
EOF
}

show_usage()
{
	cat <<EOF
Usage: $PROG [options] [*.src.rpm...]

Run $PROG -h to see the help page.
EOF
    return 1
}

# Early dry-run and debug detection
detect_debug() {
    while [ $# -gt 0 ]; do
        case "$1" in
            --debug|--dry-run|--dry-run-run)
                return 0
                ;;
        esac
        shift
    done

    return 1
}

if detect_debug "$@"; then
    echo "Command line: $*" >&2
fi


OPTS=`getopt -n $PROG -o O:,T:,x:,C:,R:,B:,N:,s:,a:,d:,n:,j:,e:,u:,H:,f,E,t,q,l:,v,V,h \
             -l outdir:,tardir:,exceptions:,base:,nvr:,skipdir:,apt-config:,repo:,dir:,mountpoints:,nums:,arches:,ssh:,user:,host:,force,abort-on-error,tar-only,quiet,verbose,debug,id:,dry-run,dry-run-run,with-srpms::,with-tars,selfie,remote-dir:,cleanup,joblog:,resume,resume-failed,retry-failed,retries:,controlmaster,sshdelay:,parallel-opts:,rsync-opts:,check-args,translate:,logdir:,limit:,version,help -- "$@"` || show_usage
eval set -- "$OPTS"

# Cleaner configuration options:
_config=
_repos=
_base=
_nums=
_arches=
_dir=
_mountpoints="$MOUNTPOINTS"
_rshell=
_ruser=
_debug=
_id=

# Builder run options:
slot_sel=
arch_sel=

reset_args() {
    config=
    repos=
    base=
    nums=
    arches=
    dir=
    mountpoints="$MOUNTPOINTS"
    rshell=
    ruser=
    debug=
    id=
    slot_sel=
}
reset_args

save_args() {
    _config="$config"
    _repos="$repos"
    _base="$base"
    _nums="$nums"
    _arches="$arches"
    _dir="$dir"
    _mountpoints="$mountpoints"
    _rshell="$rshell"
    _ruser="$ruser"
    _debug="$debug"
    _id="$id"
}

# Multi-host execution:
next_remotes=
next_args=
base_args=
remotes=
total_arches=
total_slots=0

# GNU Parallel specific
joblog=
resume=
resume_failed=
retry_failed=
retries=
controlmaster=
sshdelay=
parallel_opts=
rsync_opts=

# Main options:
nvr=
skipdir=
exceptions=
tardir=
outdir=
aerr=
force=
tar_only=
quiet=
verbose=
dry_run=
with_srpms=
fromdir=
with_tars=
selfie=
remotedir=
cleanup=
check_args_only=
cleanup_hasher=1
tscriptdir=
logdir=
limit=

print_error() {
    local fmt="${1:-}"; shift
    printf "${id:+[$id]:}$fmt\\n" "$@" >&2
}

print_info() {
    [ -n "$quiet" ] || print_error "$@"
}

append_filtered() {
    local vals="$1"
    local val="$2"
    local grepopts="${3:--wF}"
    local sepr="${4:- }"

    if [ -z "$vals" ]; then
        vals="$val"
    elif ! echo "$vals" | grep -q $grepopts "$val"; then
        vals="$vals$sepr$val"
    fi

    echo "$vals"
}

split_optval() {
    echo "$1" | sed -e 's/[,[:space:]]\+/ /g'
}

_export_args() {
    [ -z "$config" ] || echo -n " -C \"$(quote_shell "$config")\""
    [ -z "$repos" ] || echo -n " -R \"$(quote_shell "$repos")\""
    [ -z "$base" ] || echo -n " -B \"$(quote_shell "$base")\""
    [ -z "$arches" ] || echo -n " -a \"$(quote_shell "$arches")\""
    [ -z "$nums" ] || echo -n " -n \"$(quote_shell "$nums")\""
    [ -z "$dir" ] || echo -n " -d \"$(quote_shell "$dir")\""
    [ "$mountpoints" = "$MOUNTPOINTS" ] || \
        echo -n " -m \"$(quote_shell "$mountpoints")\""
    [ -z "$debug" ] || echo -n " --debug"
    [ -z "$id" ] || echo -n " --id=\"$(quote_shell "$id")\""
    echo
}
export_args() {
    local args="$(_export_args)"
    echo "${args# }"
}

_export_main_args() {
    if [ -z "$with_tars" ]; then
        [ -z "$nvr" ] || echo -n " -N \"$(quote_shell "$nvr")\""
        [ -z "$exceptions" ] || echo -n " -x \"$(quote_shell "${exceptions##*/}")\""
    fi
    [ -z "$outdir" ] || echo -n " -O \"$(quote_shell "$outdir")\""
    [ -z "$tardir" ] || echo -n " -T \"$(quote_shell "$tardir")\""
    [ -z "$logdir" ] || echo -n " -l \"$(quote_shell "$logdir")\""
    [ -z "$tar_only" ] || echo -n " -t"
    [ -z "$force" ] || echo -n " -f"
    [ -z "$quiet" ] || echo -n " -q"
    [ -z "$verbose" ] || echo -n " -v"
    echo
}
export_main_args() {
    local args="$(_export_main_args "$@")"
    echo "${args# }"
}

export_all_args() {
    local args="$(export_args)"
    [ -z "$args" ] || args="$args "
    args="$args$(export_main_args)"
    echo "$args"
}

check_args() {
    local local_args="$(export_args)"

    local ret=0
    eval "$0" --check-args $base_args $local_args || ret=$?

    if [ $ret -eq 0 ]; then
        echo "$local_args"
    fi

    return $ret
}

configure_remote() {
    local remote="$1"
    local next_args="$2"

    local allarches="$_arches"
    for arch in $arches; do
        allarches="$(append_filtered "$allarches" "$arch")"
    done

    local grps=
    for arch in $allarches; do
        [ -z "$grps" ] || grps="$grps+"
        grps="$grps$arch"
    done

    local allnums="$_nums"
    for num in $nums; do
        allnums="$(append_filtered "$allnums" "$num")"
    done

    local slot_count=$(echo "$allnums" | wc -w)
    [ $slot_count -gt 0 ] || slot_count=1

    if [ -n "$ruser" ] &&
           echo "$remote" | grep -q '^[A-Za-z0-9_-]\+$'
    then
        remote="$ruser@$remote"
    fi

    if [ -n "$rshell" ]; then
        remote="$rshell $remote"
    fi

    remote="${grps:+@$grps/}$slot_count/$remote"
    if [ -z "$tar_only" -a -z "$_arches$arches" ]; then
        print_error "WARNING: %s is expected to build any arch!" \
                    "$remote"
    fi

    echo "$remote${next_args:+ + $next_args}"
}

append_next_remotes() {
    local remote=
    if [ -n "$next_remotes" ]; then
        next_args="$(check_args)" || exit $?
        for remote in $next_remotes; do
            remote="$(configure_remote "$remote" "$next_args")"
            remotes="$(append_filtered "$remotes" "$remote" -xF "
")"
        done
        next_remotes=
        for arch in $arches; do
            total_arches="$(append_filtered "$total_arches" "$arch")"
        done
        local hostslots="$(echo "$nums" | wc -w)"
        [ "$hostslots" -gt 0 ] || hostslots=1
        total_slots=$((total_slots + hostslots))
    fi
}

while :; do
	case "$1" in
		-C|--apt-config)
            shift
            config="$1"
            ;;
		-R|--repo)
            shift
            repos="$(append_filtered "$repos" "$1" -xF "
")"
            ;;
		-O|--outdir)
            shift
            outdir="$1"
            ;;
		-T|--tardir)
            shift
            tardir="$1"
            ;;
		-t|--tar-only)
            tar_only=1
            ;;
		-x|--exceptions)
            shift
            exceptions="$1"
            ;;
		-B|--base)
            shift
            base="$1"
            ;;
        -N|--nvr)
            shift
            nvr="$1"
            ;;
        -s|--skipdir)
            shift
            skipdir="$1"
            ;;
        -n|--nums)
            shift
            case "$1" in
                :[0-9]*)
                    slot_sel="${1#:}"
                    ;;
                *)
                    for range in $(split_optval "$1"); do
                        if echo "$range" | grep -q '^[0-9]\+-[0-9]\+$'; then
                            range="$(seq ${range%-*} ${range#*-})"
                        elif ! echo "$range" | grep -q '^[0-9]\+$'; then
                            print_error "ERROR: Invalid number or number range: %s" "$range"
                            exit 1
                        fi
                        for num in $range; do
                            nums="$(append_filtered "$nums" "$num")"
                        done
                    done
                    ;;
            esac
            ;;
        -a|--arches)
            shift
            case "$1" in
                :*)
                    arch_sel="${1#:}"
                    ;;
                *)
                    for arch in $(split_optval "$1"); do
                        arches="$(append_filtered "$arches" "$arch")"
                    done
                    ;;
            esac
            ;;
        -d|--dir)
            shift
            dir="$1"
            ;;
        -m|--mountpoints)
            shift
            mountpoints="$1"
            ;;
        -e|--ssh)
            shift
            rshell="$1"
            ;;
        -u|--user)
            shift
            ruser="$1"
            ;;
        -H|--host)
            shift
            if [ -z "$remotes" -a -z "$next_remotes" ]; then
                # first -H|--host
                base_args="$(export_all_args)"
                save_args
            elif [ -n "$next_remotes" ]; then
                append_next_remotes
            fi
            for remote in $(split_optval "$1"); do
                next_remotes="$(append_filtered "$next_remotes" "$remote")"
            done
            reset_args
            ;;
        -f|--force)
            force=1
            ;;
        -E|--abort-on-error)
            aerr=1
            ;;
        -q|--quiet)
            quiet=1
            ;;
        -v|--verbose)
            verbose=1
            ;;
        --debug)
            debug=1
            cleanup_hasher=
            verbose=1
            ;;
        --id)
            shift
            id="$1"
            if [ "$id" = ":" ]; then
                id='localhost'
            fi
            ;;
        --dry-run)
            dry_run=1
            ;;
        --dry-run-run)
            dry_run=2
            ;;
        --with-srpms)
            shift
            with_srpms=1
            fromdir="$1"
            base="$1"
            ;;
        --with-tars)
            with_tars=1
            ;;
        --selfie)
            selfie=1
            ;;
        --remote-dir)
            shift
            remotedir="$1"
            ;;
        --cleanup)
            cleanup=1
            ;;
        --check-args)
            check_args_only=1
            ;;
        --joblog)
            shift
            joblog="$1"
            ;;
        --resume)
            resume=1
            ;;
        --resume-failed)
            resume_failed=1
            ;;
        --retry-failed)
            retry_failed=1
            ;;
        --retries)
            shift
            retries="$1"
            ;;
        --controlmaster)
            controlmaster=1
            ;;
        --sshdelay)
            shift
            sshdelay="$1"
            ;;
        --parallel-opts)
            shift
            [ -z "$parallel_opts" ] || \
                parallel_opts="$parallel_opts "
            parallel_opts="$parallel_opts$1"
            ;;
        --rsync-opts)
            shift
            [ -z "$rsync_opts" ] || \
                rsync_opts="$rsync_opts "
            rsync_opts="$rsync_opts$1"
            ;;
        -l|--logdir)
            shift
            logdir="$1"
            ;;
        --translate)
            shift
            tscriptdir="$1"
            ;;
        --limit)
            shift
            limit="$1"
            ;;
		-V|--version)
            print_version
            exit 0
            ;;
		-h|--help)
            show_help
            exit 0
            ;;
        --)
            shift
            break
            ;;
		*)
            print_error "ERROR: Unrecognized option: %s" "$1"
            exit 1
            ;;
	esac
	shift
done

# Process the remotes, if any
if [ -n "$next_remotes" ]; then
    append_next_remotes
fi

if [ -n "$remotes" ]; then
    # Reset args
    reset_args
    debug="$_debug"
    arches="$total_arches"

    print_info "Base args: %s" "$base_args"
    print_info "Cleaners:\\n%s" "$(echo "$remotes" | sed -e 's/^.*$/  * &/')"
    [ -z "$arches" ] || print_info "Architectures total: %s" "$arches"
fi


## Process the options

if [ -n "$tar_only" -a -z "$tardir" ]; then
    print_error "ERROR: Please, specify the tardir with -T|--tardir"
    exit 1
fi

if [ -z "$outdir" -a -z "$tar_only" ]; then
    print_error "ERROR: Please, specify the output directory with -O|--outdir"
    exit 1
fi

numarches=$(echo "$arches" | wc -w)

if [ -n "$config" ]; then
    if [ -n "$repos" ]; then
        print_error "ERROR: -C|--apt-config and -R|--repo can not be used together."
        exit 1
    fi
    if [ $numarches -gt 1 ]; then
        case "$config" in
            *%s*)
                ;;
            *)
                print_error "ERROR: In order to use -C|--apt-config with more than one architecture the path should contain %%s."
                exit 1
                ;;
        esac
    fi
fi

if [ -z "$remotes" -a -z "$tar_only" -a $numarches -gt 1 -a -z "$config" -a -z "$repos" ]
then
    print_error "ERROR: Specify -C|--apt-config or -R|--repo to use more than one architecture."
    exit 1
fi

if [ -n "$nums" -a -z "$dir" -a -z "$tar_only" ]; then
    print_error "ERROR: Specify a working directory with -d|--dir in order to use Hasher slots (subconfigs)."
    exit 1
fi

if [ -n "$exceptions" -a ! -r "$exceptions" ]; then
    print_error "ERROR: %s isn't readable." "$exceptions"
    exit 1
fi

if [ -n "$skipdir" -a ! -d "$skipdir" ]; then
    print_error "ERROR: %s is not a directory." "$skipdir"
    exit 1
fi

# Exit here if check args mode is specified
[ -z "$check_args_only" ] || exit 0


## Tempdir

tmpdir="$(mktemp -d --tmpdir $PROG.XXXX)"
if [ -z "$tmpdir" ]; then
    print_error "ERROR: Unable to create temporary directory."
    exit 2
fi

onexit() {
    if [ -n "$tmpdir" ]; then
        if [ -z "$debug" ]; then
            rm -rf "$tmpdir"
        else
            echo "Leaving $tmpdir for debugging." >&2
        fi
    fi
}
trap onexit EXIT INT


# Generate/update the skiplists

[ -n "$slot_sel" ] || \
    print_info "Generating/Updating the skiplists..."

find_trscript() {
    local pkgname="$1"
    if [ -n "$tscriptdir" ]; then
        if [ -e "${tscriptdir%/}/$pkgname.sed" ]; then
            echo "${tscriptdir%/}/$pkgname.sed"
            return 0
        fi
        pkgname="${pkgname%-*}"; pkgname="${pkgname%-*}"
        if [ -e "${tscriptdir%/}/$pkgname.sed" ]; then
            echo "${tscriptdir%/}/$pkgname.sed"
            return 0
        fi
    fi

    return 1
}

check_update_skiplist() {
    local all_list="$1"
    local part_lists="$2"

    local pkgname="${all_list##*/}"
    pkgname="${pkgname%.all.skiplist}"

    local trscript=
    local makeit=
    if [ -e "$all_list" ]; then
        if [ -n "$force" -a -z "$with_tars" ]; then
            [ -z "$verbose" ] || \
                print_info "Forcibly recreating %s..." "$all_list"
            makeit=1
        else
            echo "$part_lists" | \
                while read -r lst; do
                    if [ "$lst" -nt "$all_list" ]; then
                        [ -z "$verbose" ] || \
                            print_info "%s is newer than %s." \
                                       "$lst" "$all_list"
                        makeit=1
                        break
                    fi
                done

            if trscript="$(find_trscript "$pkgname")"; then
                if [ "$trscript" -nt "$all_list" ]; then
                    makeit=1
                    [ -z "$verbose" ] || \
                        print_info "List translation script %s is newer than %s..." "$trscript" "$all_list"
                fi
            fi
        fi
    else
        [ -z "$verbose" ] || \
            print_info "%s doesn't exist." "$all_list"
        makeit=1
    fi

    if [ -n "$makeit" ]; then
        if [ -n "$dry_run" ]; then
            all_list="$tmpdir/dryrun/$all_list"
            mkdir -p "${all_list%/*}"
        fi

        echo "$part_lists" | (
            while read -r lst; do
                if [ -n "$makeit" ]; then
                    sort "$lst" >"$tmpdir/$pkgname.all.skiplist"
                    makeit=
                else
                    sort "$lst" | \
                        comm -12 - "$tmpdir/$pkgname.all.skiplist" \
                         >"$tmpdir/$pkgname.all.skiplist.next"
                    mv "$tmpdir/$pkgname.all.skiplist.next" \
                       "$tmpdir/$pkgname.all.skiplist"
                fi
            done
        )

        if [ -n "$trscript" ]; then
            [ -z "$verbose" ] || \
                print_info "Applying %s." "$trscript"
            sed -i -f "$trscript" "$tmpdir/$pkgname.all.skiplist"
        fi

        grep -Fv -e '.gear' -e '.spec' \
             "$tmpdir/$pkgname.all.skiplist" >"$all_list"

        [ -z "$verbose" ] || \
            print_info "%s updated." "$all_list"
        return 0
    fi

    return 1
}

if [ $# -eq 0 ]; then
    [ -z "$dry_run" ] || mkdir "$tmpdir/dryrun"
    if [ -n "$nvr" ]; then
        cat >"${dry_run:+$tmpdir/dryrun/}$nvr.all.skiplist"
        echo "$nvr.all.skiplist" >>"$tmpdir/input"
    else
        cat >>"$tmpdir/input"
    fi
else
    nvr_parts=
    while [ $# -gt 0 ]; do
        if [ -n "$skipdir" ]; then
            find "$skipdir" -mindepth 1 -maxdepth 1 \
                 -name "$1.*.skiplist" -a ! -name '*.all.skiplist' \
               >>"$tmpdir/input"
            shift
            continue
        fi

        case "$1" in
            *.skiplist)
                if [ -n "$dry_run" ]; then
                    case "$1" in
                        *.all.skiplist)
                            mkdir -p "$tmpdir/dryrun/${1%/*}"
                            cat "$1" >"$tmpdir/dryrun/$1"
                            echo "$tmpdir/dryrun/$1" >>"$tmpdir/input"
                            ;;
                        *)
                            echo "$1" >>"$tmpdir/input"
                            ;;
                    esac
                else
                    echo "$1" >>"$tmpdir/input"
                fi
                ;;
            *.tar)
                echo "$1" >>"$tmpdir/input"
                ;;
            *)
                if [ -d "${1%/}" ]; then
                    find "$1" -mindepth 1 -maxdepth 1 \
                         -name '*.skiplist' -a ! -name '*.all.skiplist' \
                       >>"$tmpdir/input"
                elif [ -r "$1" ]; then
                    if [ -n "$nvr" ]; then
                        [ -z "$nvr_parts" ] || \
                            nvr_parts="$nvr_parts
"
                        nvr_parts="$nvr_parts$1"
                    else
                        cat "$1" >>"$tmpdir/input"
                    fi
                else
                    print_error "ERROR: Unable to read the list from %s." "$1"
                    exit 3
                fi
                ;;
        esac
        shift
    done

    if [ -n "$nvr_parts" ]; then
        if check_update_skiplist "$nvr.all.skiplist" "$nvr_parts"; then
            echo "$nvr.all.skiplist" >>"$tmpdir/input"
        fi
    fi
fi

if [ ! -e "$tmpdir/input" ]; then
    print_info "Nothing to do. Exit."
    exit 0
fi

sort -u "$tmpdir/input" >"$tmpdir/input.new"
mv "$tmpdir/input.new" "$tmpdir/input"

list_arches() {
    local pkgarches="$(echo "$1" | sed 's/^.*\.\([^.]\+\)\.skiplist$/\1/' | sort -u | tr '\n' ' ')"
    echo "${pkgarches% }"
}

empty_count=0
add_todo() {
    local all_list="$1"
    local arch_lists="${2:-}"

    local pkgname="${all_list##*/}"
    pkgname="${pkgname%.all.skiplist}"

    local grps=
    local pkgarches=

    if [ -z "$tar_only" ] ;then
        pkgarches="$(list_arches "$arch_lists")"
        for pkgarch in ${pkgarches:-$arches}; do
            if echo "$arches" | grep -qwF "$pkgarch"; then
                [ -z "$grps" ] || grps="$grps+"
                grps="$grps$pkgarch"
            fi
        done
    fi

    if [ -n "$grps" ]; then
        grps="$(echo "$grps" | tr '+' '\n' | sort -u | tr '\n' '+')"
        grps="${grps%+}"
    fi

    local newlist=
    local oldtar=
    local oldsrpm=
    if [ -n "$arch_lists" ] && \
           check_update_skiplist "$all_list" "$arch_lists"
    then
        oldtar=1
        newlist=1
        all_list="${dry_run:+$tmpdir/dryrun/}$all_list"
    elif [ ! -e "$tardir/$pkgname.tar" -o \
           "$all_list" -nt "$tardir/$pkgname.tar" ]
    then
        oldtar=1
    elif [ -z "$tar_only" -a \( \
           ! -e "$outdir/$pkgname.src.rpm" -o \
           "$tardir/$pkgname.tar" -nt "$outdir/$pkgname.src.rpm" \) ]
    then
        oldsrpm=1
    fi

    if [ ! -s "$all_list" ]; then
        [ -z "$verbose" ] || \
            print_info "Skip empty skiplist %s." "$all_list"
        empty_count=$((empty_count + 1))
        return 0
    fi

    if [ -n "$force" -o -n "$newlist" -o -n "$oldtar" -o -n "$oldsrpm" ]
    then
        if [ -n "$arch_lists" -a -z "$grps" -a -z "$tar_only" ]; then
            print_error "ERROR: No supported architecture found for %s. Use -a and or -H to add some of: %s." "$pkgname" "${pkgarches// /, }"
            exit 1
        fi

        if [ -n "$force" -o -n "$newlist" ]; then
            [ -z "$verbose" ] || \
                print_info "Add new/updated %s" "$all_list"
        elif [ -z "$with_tars" -a \( -n "$force" -o -n "$oldtar" \) ]; then
            [ -z "$verbose" ] || \
                print_info "Need to (re)create %s" "$tardir/$pkgname.tar"
        else
            [ -z "$verbose" ] || \
                print_info "Need to (re)create %s" "$outdir/$pkgname.src.rpm"
        fi

        if [ -n "$with_tars" ]; then
            if [ -n "$oldtar" ]; then
                print_error "ERROR: Old TAR found for %s in --with-tars mode" \
                            "$all_list"
                return 1
            else
                echo "$pkgname.tar${grps:+@$grps}" >>"$tmpdir/input.todo"
            fi
        else
            echo "$all_list${grps:+@$grps}" >>"$tmpdir/input.todo"
        fi
    fi
}

arch_lists=; prev=; prev_name=; pkgs_added=0
while read -r ln; do
    if [ -n "$limit" ]; then
        if [ $pkgs_added -ge $limit ]; then
            print_info "Limit the work plan by %d packages as was asked." \
                       "$limit"
            break
        fi
    fi

    case "$ln" in
        *.tar)
            echo "$ln" >>"$tmpdir/input.todo"
            pkgs_added=$((pkgs_added + 1))
            continue
            ;;
        *.all.skiplist)
            add_todo "$ln"
            pkgs_added=$((pkgs_added + 1))
            continue
            ;;
    esac

    pkgname="${ln##*/}"
    pkgname="${pkgname%.*.skiplist}"

    if [ "$pkgname" = "$prev_name" ]; then
        # Next list for the same package
        arch_lists="$arch_lists
$ln"
    else
        # New package
        [ -z "$arch_lists" ] || \
            add_todo "${prev%.*.skiplist}.all.skiplist" "$arch_lists"
        arch_lists="$ln"; prev="$ln"; prev_name="$pkgname"
        pkgs_added=$((pkgs_added + 1))
    fi
done <"$tmpdir/input"

if [ -n "$arch_lists" ]; then
    add_todo "${prev%.*.skiplist}.all.skiplist" "$arch_lists"
fi

touch "$tmpdir/input.todo"
sort -u "$tmpdir/input.todo" >"$tmpdir/input.todo.new"
mv "$tmpdir/input.todo.new" "$tmpdir/input.todo"

input="$tmpdir/input.todo"
touch "$input"

[ -n "$slot_sel" ] || \
    print_info "Finished generating/updating the skiplists."

numremotes=0
numpkgs=$(cat "$input" | wc -l)
numslots=$(echo "$nums" | wc -w)
if [ -n "$remotes" ]; then
    numremotes=$(echo "$remotes" | wc -l)
fi

if [ $numpkgs -eq 0 ]; then
    print_info "Nothing to do. Exit."
    exit 0
else
    if [ -z "$slot_sel" ]; then
        print_info "%s packages in the list (%d empty lists skipped)." \
                   $numpkgs $empty_count
    fi
fi

multi=
if [ $numpkgs -gt 1 -o $numremotes -gt 0 ]; then
    multi=1
fi


# Generate APT configs from repository addresses:
if [ -n "$repos" ]; then
    for arch in ${arches:-"$DEFARCH"}; do
        cat <<EOF >"$tmpdir/apt.$arch.conf"
Dir::Etc::SourceList "$tmpdir/sources.$arch.list";
Dir::Etc::SourceParts "/var/empty";
EOF
        for repo in $repos; do
            case "$repo" in
                /*)
                    repo="file://$repo"
                    ;;
                *://*)
                    ;;
                *)
                    repo="file://$repo"
                    ;;
            esac
            cat <<EOF >>"$tmpdir/sources.$arch.list"
rpm $repo $arch $CONPONENTS
rpm $repo noarch $CONPONENTS
EOF
        done
    done
fi


## Functions

hshdir() {
    local num="${1:-}"
    if [ -z "$dir" ]; then
        return 0
    elif [ -z "$num" ]; then
        echo "$dir"
    else
        case "$dir" in
            *%d*)
                printf "$dir\\n" $num
                ;;
            *)
                echo "$dir/$num"
                ;;
        esac
    fi
}

defhshdir() {
    local wdir=
    if [ -e "$HOME/.hasher/config" ]; then
        wdir="$(sed -n -e 's/^workdir=//p' $HOME/.hasher/config)"
        if [ -n "$wdir" ]; then
            wdir="$(eval echo $wdir)" # FIXME
        fi
    fi

    [ -n "$wdir" ] || wdir="$HOME/hasher"

    echo "$wdir"
}

hshrun_base() {
    local num="$1"; shift
    local arch="$1"; shift
    local hshfunc="$1"; shift

    local wdir="$(hshdir "$num")"
    if [ -n "$wdir" ]; then
        mkdir -p "$wdir" || return $?
    fi

    (
        exec <&-
        export LANG=C
        ${arch:+setarch "$arch"} \
        "$hshfunc" ${wdir:+$wdir} \
                   ${num:+--number=$num} \
                   ${mountpoints:+--mountpoints=$mountpoints} \
                   --without-stuff \
                   --no-wait-lock \
                   ${arch:+--target=$arch} \
                   "$@"
    )
}

hshrun_hsh() {
    local num="$1"; shift
    local arch="$1"; shift
    local hshfunc="$1"; shift

    local wconfig="$config"

    if [ -n "$wconfig" ]; then
        if [ -n "$arch" ]; then
            case "$wconfig" in
                *%s*)
                    wconfig="$(printf "$wconfig" "$arch")"
                    ;;
            esac
        fi
    elif [ -n "$repos" ]; then
        wconfig="$tmpdir/apt.${arch:-$DEFARCH}.conf"
    fi

    hshrun_base "$num" "$arch" \
                "$hshfunc" --lazy-cleanup \
                           ${wconfig:+--apt-config="$wconfig"} \
                           "$@"
}

hshcopysrpm() {
    local pkgname="$1"
    local outdir="$2"
    local num="${3:-}"

    local wdir="$(hshdir "$num")"

    hsh-run ${wdir:+$wdir} \
            ${num:+--number=$num} \
            --no-wait-lock -- \
            cp -a "/usr/src/in/srpm/$pkgname.src.rpm" \
                  /.out/

    [ -n "$wdir" ] || wdir="$(defhshdir)"

    cp -a "${wdir%/}/chroot/.out/$pkgname.src.rpm" \
          "${outdir%/}/"
}

prefix_out() {
    sed -e "s/^.*\$/$1 &/" >&2
}

hsh_cleanup() {
    local num="${1:-}"

    local wdir="$(hshdir "$num")"

    hsh ${wdir:+$wdir} \
        ${num:+--number=$num} \
        --no-wait-lock \
        --cleanup-only
}

detect_ubt() {
    echo "$1" | sed -n -e 's/^.*\(\.\(M[0-9]\+[PC]\.[0-9]\+\|S[0-9]\+\(\.[0-9]\+\)\?\)\)\.src\.rpm$/\1/p'
}

cleanup_one() {
    local inarg="$1"
    local num="${2:-}"
    local arch="${3:-}"

    local pkgname=
    local skiplist=
    local pkgtar=
    case "$inarg" in
        *.skiplist)
            skiplist="$1"
            pkgname="${skiplist##*/}"
            pkgname="${pkgname%.*.skiplist}"
            if [ ! -e "$skiplist" -a -n "$skipdir" -a \
                   -e "$skipdir/$skiplist" ]; then
                skiplist="$skipdir/$skiplist"
            fi
            if [ ! -r "$skiplist" ]; then
                print_error "ERROR: File is not accessible: %s." \
                            "$skiplist"
                return 1
            fi
            ;;
        *.tar)
            pkgtar="$1"
            pkgname="${pkgtar%.tar}"
            ;;
        *)
            print_error "ERROR: Unknown input type: %s." "$inarg"
            return 1
            ;;
    esac

    local log="$tmpdir/$pkgname.log"
    local prefix="${num:+#$num }$pkgname"

    local no_verbose=
    [ -n "$verbose" ] || no_verbose=1

    local tarname="$tardir/$pkgname.tar"

    local status=0
    local ret=0
    if [ -z "$pkgtar" -a \
         \( ! -e "$tardir/$pkgname.tar" -o \
            "$skiplist" -nt "$tardir/$pkgname.tar" -o \
            -n "$force" \) ]
    then
        local tarname_abs=
        if [ -n "$dry_run" ]; then
            tarname="$tmpdir/$pkgname.tar"
            tarname_abs="$tarname"
        else
            tarname_abs="$(pwd)/$tarname"
        fi

        if [ -n "$exceptions" ]; then
            [ -z "$verbose" ] || \
                print_info "[%s] Using exceptions file %s" \
                           "$prefix" "$exceptions"
            grep -v -e '^\^' -e '\$$' "$exceptions" \
                 >"$tmpdir/exceptions.plain"
            grep -e '^\^' -e '\$$' "$exceptions" \
                 >"$tmpdir/exceptions.regexp"
            grep -Fvf "$tmpdir/exceptions.plain" "$skiplist" | \
                grep -vf "$tmpdir/exceptions.regexp" | \
                grep -Fv -e '.gear' -e '.spec' \
                     >"$tmpdir/$pkgname.skiplist.filtered" \
                     2>"$log.grep" ||:
        else
            grep -Fv -e '.gear' -e '.spec' "$skiplist" \
                 >"$tmpdir/$pkgname.skiplist.filtered" \
                 2>"$log.grep" ||:
        fi

        if [ ! -s "$log.grep" ]; then
            skiplist="$tmpdir/$pkgname.skiplist.filtered"
        else
            cat "$log.grep" | prefix_out "${id:+[$id]:}[$prefix]" >&2
            print_error "[%s] ERROR: Unable to filter the skiplist." \
                        "$prefix"
            ret=1
        fi

        if [ $ret -eq 0 -a -s "$skiplist" ]; then
            print_info "[%s] Importing the package to git..." "$prefix"
            mkdir "$tmpdir/$pkgname.git"
            (
                absbase="${base:+$(readlink -f "$base")}"
                cd "$tmpdir/$pkgname.git"
                git init
                git config user.name "Separ Ator"
                git config user.email "<$PROG@altlinux.org>"
                gear-srpmimport -v "${absbase:+${absbase%/}/}$pkgname.src.rpm"
            ) >"$log" 2>&1 || ret=$?

            if [ $ret -ne 0 ]; then
                print_error "[%s] ERROR: Unable to gear-srpmimport." "$prefix"
            fi
        fi

        if [ $ret -eq 0 -a -s "$skiplist" ]; then
            print_info "[%s] Truncating unused files..." "$prefix"
            (
                cd "$tmpdir/$pkgname.git"
                find ./ \
                     -path './.git' -prune -o \
                     -path './.gear*' -prune -o \
                     -type f -print \
                   >"$tmpdir/$pkgname.filelist" \
                   2>"$log.find"
            ) >>"$log" 2>&1 || ret=$?

            if [ ! -s "$log.find" ]; then
                # No errors reported
                ret=0
            else
                cat "$log.find" | prefix_out "${id:+[$id]:}[$prefix]" >&2
                ret=1
            fi

            if [ $ret -eq 0 -a -s "$tmpdir/$pkgname.filelist" ]; then
                (
                    cd "$tmpdir/$pkgname.git"
                    while read -r n; do
                        # Local exceptions:
                        case "$n" in
                            *.spec)
                                continue
                                ;;
                        esac

# FIXME:
# /web-platform/tests/css/CSS2/syntax/support/'green block.png
# /attr-meta-http-equiv-refresh/support/foo'bar
# /uriloader/exthandler/tests/mochitest/file_with[funny_name.webm
# /exthandler/tests/mochitest/file_with[funny_name.webm^headers^

                        grep -F "/$n" "$tmpdir/$pkgname.filelist" | \
                          grep "/${n##*/}\$" | \
                            awk '{ print length, $0 }' | \
                            sort -n | \
                            cut -d" " -f2- | \
                            head -1 | \
                            xargs -i sh -c \
                                  'echo -n > "{}" && touch -t0001010000 "{}"'
                    done
                ) <"$skiplist" >>"$log" 2>&1 || ret=$?
            fi

            if [ $ret -ne 0 ]; then
                print_error "[%s] ERROR: Failed to truncate files." \
                            "$prefix"
            fi
        fi

        if [ $ret -eq 0 ]; then
            if [ -s "$skiplist" ]; then
                status="$(cd "$tmpdir/$pkgname.git"; git diff --name-only | wc -l)"
            fi
            if [ "$status" -eq 0 ]; then
                print_info "[%s] No changes." "$prefix"
            else
                print_info "[%s] %d files changed." "$prefix" "$status"
            fi
        fi

        if [ $ret -eq 0 -a "$status" -ne 0 ]; then
            print_info "[%s] Writing modified package to %s" \
                       "$prefix" "$tarname"
            (
                cd "$tmpdir/$pkgname.git"
                git commit -a -m "Truncate unused source files."
                mkdir -p "${tarname_abs%/*}"
                gear -v "$tarname_abs"
            ) >>"$log" 2>&1 || ret=$?

            if [ $ret -eq 0 ]; then
                print_info "[%s] Done writing %s." "$prefix" "$tarname"
            else
                print_error "[%s] ERROR: Failed to write %s." \
                            "$prefix" "$tarname"
            fi

            if [ -z "$debug" ]; then
                [ -z "$verbose" ] || \
                    print_info "[%s] Deleting the git repo..." "$prefix"
                rm -rf "$tmpdir/$pkgname.git"
            fi
        fi
    fi

    local pkg="${base:+${base%/}/}$pkgname.src.rpm"

    local outpkgdir=
    if [ -z "$dry_run" ]; then
        outpkgdir="$outdir"
    else
        outpkgdir="$tmpdir"
    fi

    if [ $ret -eq 0 -a -n "$pkgtar" ]; then
        if [ -e "$pkgtar" ]; then
            tarname="$pkgtar"
        elif [ -e "$tardir/$pkgtar" ]; then
            tarname="$tardir/$pkgtar"
        fi

        if [ ! -e "$tarname" ]; then
            print_error "[%s] ERROR: TAR package %s not found." \
                        "$prefix" "$pkgtar"
            return 1
        fi
    fi

    if [ $ret -eq 0 -a -z "$tar_only" -a \
           -e "$tarname" -a \
           \( ! -e "$outdir/$pkgname.src.rpm" -o \
           -n "$force" -o \
           "$tarname" -nt "$outdir/$pkgname.src.rpm" \) ]
    then
        prefix="$prefix${arch:+ $arch}"
        print_info "[%s] Creating build environment..." "$prefix"
        hshrun_hsh "$num" "$arch" \
               hsh --rebuild-prog=/bin/true \
                   --packager "Separ Ator <$PROG@altlinux.org>" \
                   --query-repackage \
                   "$pkg" >>"$log" 2>&1 || \
            ret=$?

        if [ $ret -eq 0 ]; then
            print_info "[%s] Done creating build environment." "$prefix"
        else
            print_error "[%s] ERROR: Failed to create build environment." \
                        "$prefix"
        fi

        if [ $ret -eq 0 ]; then
            # Build without recreating the chroot.
            local ubt="$(detect_ubt "$pkg")"
            [ -n "$ubt" ] || ubt="%nil"
            print_info "[%s] Building source package..." "$prefix"
            hshrun_base "$num" "$arch" \
                   hsh-rebuild --source-only --args="--define=\"ubt $ubt\"" "$tarname" >>"$log" 2>&1 || \
                ret=$?

            if [ $ret -eq 0 ]; then
                print_info "[%s] Done building source package." "$prefix"
                mkdir -p "$outpkgdir"
                if hshcopysrpm "$pkgname" "$outpkgdir" "$num"; then
                    print_info "[%s] Copy modified SRPM package to %s" \
                               "$prefix" "${outpkgdir%/}/"
                else
                    print_error "[%s] ERROR: Failed to copy new SRPM package." "$prefix"
                    ret=1
                fi
            else
                print_error "[%s] ERROR: Failed to build the source package." \
                            "$prefix"
            fi
        fi

        if [ -n "$cleanup_hasher" ]; then
            print_info "[%s] Cleaning-up hasher..." "$prefix"
            local cret=0
            hsh_cleanup "$num" >>"$log" 2>&1 || cret=$?
            if [ $cret -eq 0 ]; then
                print_info "[%s] Done." "$prefix"
            else
                print_error "[%s] ERROR: Hasher cleanup failed." \
                            "$prefix"
            fi
            [ $ret -ne 0 ] || ret=$cret
        fi
    fi

    if [ $ret -ne 0 ]; then
        if [ -z "$quiet" ]; then
            tail "$log" | prefix_out "${id:+[$id]:}[$prefix]" >&2
        fi
    fi

    if [ -n "$logdir" -a -z "$dry_run" ]; then
        mkdir -p "${logdir%/}" ||:
        cat "$log" >"${logdir%/}/$pkgname.log" ||:
    fi

    if [ -z "$debug" ]; then
        rm -f "$tmpdir/$pkgname.skiplist.filtered" \
              "$tmpdir/$pkgname.filelist" \
              "$log" "$log.grep" "$log.find"
    fi

    return $ret
}


## Main

quote_remotes() {
    echo "$remotes" | (
        sshlogins=
        while read -r remote; do
            [ -z "$sshlogins" ] || sshlogins="$sshlogins "
            sshlogins="${sshlogins}-S \"$(quote_shell "$remote")\""
        done
        echo "$sshlogins"
    )
}

print_and_run() {
    if [ -n "$dry_run" ]; then
        echo "$@" >&2
    fi
    eval "$@"
}

basefile_path() {
    local fpath="$1"

    [ -n "$fpath" ] || return 0

    case "$fpath" in
        /*)
            fpath="${fpath%/*}/./${fpath##*/}"
            ;;
        ./*)
            fpath="$PWD/$fpath"
            ;;
        *)
            fpath="$PWD/./$fpath"
            ;;
    esac

    echo "$fpath"
}

if [ -n "$multi" -a \( "$numslots" -gt 1 -o "$numremotes" -gt 0 \) ]
then
    # Translate arguments for GNU Parallel
    dry_run_run=
    if [ "${dry_run:-0}" -gt 1 ]; then
        dry_run_run=1
        dry_run=
    fi

    no_dry_run=
    [ -n "$dry_run" ] || no_dry_run=1

    no_dry_run_run=
    [ -n "$dry_run_run" ] || no_dry_run_run=1

    no_tar_only=
    [ -n "$tar_only" ] || no_tar_only=1

    no_with_tars=
    if [ -z "$with_tars" ]; then
        no_with_tars=1
    fi

    no_remotes=
    if [ "$numremotes" -gt 0 ]; then
        print_info "Activate parallel mode for %d hosts, %d slots" \
                   "$numremotes" "$total_slots"
        nums=
    else
        print_info "Activate parallel mode for %d slots" "$numslots"
        no_remotes=1
    fi

    if [ -z "$base_args" ]; then
        base_args="$(export_all_args)"
    fi

    self="$0"
    basefile=
    if [ -n "$selfie" ]; then
        case "$self" in
            /*)
                basefile="${self%/*}/./${self##*/}"
                self="./${self##*/}"
                ;;
            *)
                basefile="$self"
                ;;
        esac
    fi

    exceptions="$(basefile_path "$exceptions")"

    basefile_opt=
    case "$remotedir" in
        ...)
            basefile_opt=--transferfile
            ;;
        *)
            basefile_opt=--basefile
            ;;
    esac

    print_and_run \
    parallel_alt --will-cite \
             ${dry_run:+--dry-run} \
             ${nums:+-j $numslots} \
             ${aerr:+--halt soon,fail=1} \
             ${joblog:+--joblog "\"$(quote_shell "$joblog")\""} \
             ${resume:+--resume} \
             ${resume_failed:+--resume-failed} \
             ${retry_failed:+--retry-failed} \
             ${retries:+--retries "\"$(quote_shell "$retries")\""} \
             ${controlmaster:+--controlmaster} \
             ${sshdelay:+--sshdelay "\"$(quote_shell "$sshdelay")\""} \
             --line-buffer \
             ${remotes:+--plus --hostgroups --rsync-opts -azR $rsync_opts} \
             ${remotes:+$(quote_remotes)} \
             ${remotes:+${no_dry_run:+${basefile:+$basefile_opt "\"$(quote_shell "$basefile")\""}${exceptions:+ $basefile_opt "\"$(quote_shell "$exceptions")\""}}} \
             ${remotes:+${no_with_tars:+--transferfile {1\}}${with_srpms:+ --transferfile "\"$(quote_shell "${fromdir:+${fromdir%/}/}{=1 s:^.*/::, s:\.all\.skiplist\$::, s:\.tar\$::  =}.src.rpm")\""}${with_tars:+ --transferfile "\"$(quote_shell "${tardir%/}/{=1 s:^.*/::, s:\.all\.skiplist\$::, s:\.tar\$::  =}.tar")\""}} \
             ${remotedir:+--workdir "\"$(quote_shell "$remotedir")\""} \
             ${cleanup:+--cleanup} \
             ${no_dry_run_run:+${remotes:+${no_with_tars:+ --return "\"$(quote_shell "${tardir%/}/{=1 s:^.*/::, s:\.all\.skiplist\$::, s:\.tar\$:: =}.tar")\""}${no_tar_only:+ --return "\"$(quote_shell "${outdir%/}/{=1 s:^.*/::, s:\.all\.skiplist\$::, s:\.tar\$:: =}.src.rpm")\""}${logdir:+ --return "\"$(quote_shell "${logdir%/}/{=1 s:^.*/::, s:\.all\.skiplist\$::, s:\.tar\$:: =}.log")\""}}} \
             ${no_remotes:+-C @} \
             ${parallel_opts:+$parallel_opts} \
             eval "\"\\\"'$self'${dry_run_run:+ --dry-run} $(quote_shell $(quote_shell "$base_args"))${remotes:+ --id \\\\\\\"{host\}\\\\\\\" {localargs\}} ${nums:+-n :{%\}}${remotes:+-n :{localslot\} ${no_tar_only:+-a :{grps\}}}${no_remotes:+${no_tar_only:+ -a :{=2 s/\\\\+.*\\$// =\}}} {1}\\\"\"" \
             :::: "\"$input\""

    exit $?
fi

# Selecting a slot if :N was passed
num=
if [ -n "$nums" ]; then
    num="$(echo "$nums" | cut -f ${slot_sel:-1} -d' ')"
    if [ -z "$num" ]; then
        print_error "BUG: Unable to select slot"
        exit 10
    fi
fi

if [ -n "$arch_sel" ]; then
    for a in ${arch_sel/+/ }; do
        if [ "$a" = "$DEFARCH" ]; then
            arch_sel="$DEFARCH"
            break
        fi
    done
    arch_sel="${arch_sel%%+*}"
    [ -z "$verbose" ] || \
        print_info "Select architecture: %s" "$arch_sel"
fi

ret=0
while read -r ln; do
    _arch_sel="${ln##*@}"
    if [ "$_arch_sel" != "$ln" ]; then
        _arch_sel="${_arch_sel%%+*}"
        ln="${ln%@*}"
    else
        _arch_sel=
    fi

    ret=0
    cleanup_one "$ln" "$num" "${arch_sel:-$_arch_sel}" || ret=$?
    if [ $ret -ne 0 ]; then
        if [ "$numpkgs" -gt 1 ]; then
            if [ -n "$aerr" ]; then
                print_info "Abort on error as was asked."
                exit $ret
            fi
        else
            exit $ret
        fi
    fi
done <"$input"

exit $ret
