#!/bin/sh

# Copyright (c) 2024 Aleksey Cheusov <cheusov@NetBSD.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# user-settable variables
: ${FTP_CMD:=ftp}
: ${FTP_CALL:=$FTP_CMD -v -o}

: ${CKSUM_CMD:=cksum}
: ${CKSUM_CALL:=$CKSUM_CMD -a}

: ${TAR_CMD:=tar}
: ${TAR_OPTS:=-z}

###
set -o pipefail
set -e

verbose() {
    # $1 -- verbose message
    if test -z "$quiet"; then
	printf '%s\n' "$1" 1>&2
    fi
}

: ${opsys:=$(uname -s)}
: ${release:=$(uname -r)}
: ${machine:=$(uname -m)}

while getopts 'c:Cd:Di:I:m:qr:s:u:x:X:' opt; do
    case "$opt" in
	c)
	    cache_dir="$OPTARG";;
	C)
	    remove_cache_dir=1;;
	d)
	    root_dir="$OPTARG";;
	D)
	    download_only=1;;
	i)
	    included_sets="$included_sets|^$OPTARG"'$';;
	I)
	    included_sets="$included_sets|^$OPTARG.*"'$';;
	m)
	    machine="$OPTARG";;
	q)
	    quiet=1;;
	r)
	    release="$OPTARG";;
	s)
	    opsys="$OPTARG";;
	u)
	    url_to_cksum="$OPTARG";;
	x)
	    excluded_sets="$excluded_sets|^$OPTARG"'$';;
	X)
	    excluded_sets="$excluded_sets|^$OPTARG.*"'$';;
	\?)
	    exit 1;;
    esac
done
shift `expr $OPTIND - 1`

if test -z "$download_only"; then
    if test -z "$root_dir"; then
	echo "Option -r is mandatory" 1>&2
	exit 2
    fi
    if ! test -d "$root_dir"; then
	echo "Root FS directory $root_dir does not exist" 1>&2
	exit 2
    fi
fi

if test -z "$cache_dir"; then
    echo "Option -c is mandatory" 1>&2
    exit 2
fi

if ! test -d "$cache_dir"; then
    echo "Cache directory $cache_dir does not exist" 1>&2
    exit 2
fi

if test -z "$url_to_cksum"; then
    case "$opsys" in
	NetBSD)
	    url_to_cksum="http://cdn.netbsd.org/pub/NetBSD/NetBSD-$release/$machine/binary/sets/SHA512";;
	OpenBSD)
	    url_to_cksum="https://cdn.openbsd.org/pub/OpenBSD/$release/$machine/SHA256";;
	*)
	    echo "Unsupported OS $opsys" 1>&2
	    exit 3;;
    esac
fi

url_dir="${url_to_cksum%/*}"

cd "$cache_dir"

if echo "$url_to_cksum" | grep -q ://; then
    cksum_fn="$cache_dir/cksums"
    $FTP_CALL "$cksum_fn" "$url_to_cksum"
else
    cksum_fn="$url_to_cksum"
fi

normalize_cksum_file() {
    awk -F'[ ()=]+' \
	-v excluded_sets="$excluded_sets" \
	-v included_sets="$included_sets" \
'
BEGIN {
    sub(/^[|]/, "", included_sets)
    sub(/^[|]/, "", excluded_sets)
}

{
    set = $2
    sub(/[.](tar.*|tgz|tbz)$/, "", set)
}

$2 !~ /([.](tar|tgz|tbz))/ || \
    (excluded_sets != "" && match(set, excluded_sets)) || \
    (included_sets != "" && !match(set, included_sets)) \
{
    next
}

{
    $1 = $1
    print $0
}' "$@"
}

download_set() {
    # $1 -- set file name
    if ! test -r "$1"; then
	$FTP_CALL "$1" "$url_dir/$set"
    fi
}

cksum_check(){
    # $1 -- cksum_type
    # $2 -- set file name
    # $3 -- cksum
    verbose "$CKSUM_CALL $1 $2"
    file_cksum=`$CKSUM_CALL "$1" "$2" | cut -d' ' -f4`
    if test "$file_cksum" != "$3"; then
	echo "$CKSUM_CALL $1 $2 failed" 1>&2
	exit 4
    fi
}

unpack_set() {
    # $1 -- set file name
    if test -z "$download_only"; then
	verbose "unpacking $set"
	$TAR_CMD $TAR_OPTS -C "$root_dir" -xpf "$set"
	touch "$cache_dir/$set.unpacked.done"
    fi
}

process_sets(){
    # stdin line: cksum_type set_file_name cksum
    while read cksum_type set cksum; do
	if test -f "$cache_dir/$set.unpacked.done"; then
	    verbose "$set already downloaded and unpacked, so skipped"
	    continue
	fi

	download_set "$set"
	cksum_check "$cksum_type" "$set" "$cksum"
	unpack_set "$set"
    done
}

remove_cache_dir() {
    if test -n "$remove_cache_dir"; then
	rm -rf "$cache_dir"
    fi
}

normalize_cksum_file "$cksum_fn" | process_sets
remove_cache_dir
