#!/bin/sh -X # no runnable script, just for editors
# set -eu is assumed.
# (Note that in ALT /bin/sh is a bash with a few features turned off.)
# (TODO: make sure that ALT's /bin/sh is enough;
# use the lightweight /bin/sh in the actual test scripts.)
#
# At least the syntax in nginxstart() requires bash >= 4 (&>>).

# TESTDIR is usually set in all the testscripts sourcing framework.
. $TESTDIR/framework-without-repo

# for debugging failed downloads
print_failed_lists()
{
    find "$APT_LISTS_DIR" -type f -name '*.FAILED' -print0 |
	xargs --no-run-if-empty -0 tail -v -n+1
}

print_debug_connect()
{
    pushd "$APT_DEBUG_CONNECT_DIR" >/dev/null
    find ./ -type f -print0 | sort -z |
	xargs --no-run-if-empty -0 tail -v -n+1
    popd >/dev/null
}

aptcdrom() { runapt apt-cdrom "${CDROM_OPTS[@]}" "$@"; }

# Input:
# * args: orig_dir repo_dir [repo_date]
#   (orig_dir may be empty if we just need to re-generate without copying rpms.)
# * a few global vars
#
# Output/effects:
# * files in $repo_dir
# * global var REPO_COMPR_EXT (the extension for the compressed pkglists there)
generaterepository() {
	local orig_dir="$1"
	local repo_dir="$2"
	local repo_date="${3-}"

	local label="$GB_REPO_LABEL"
	if [ -z "$label" ]; then
		label="apt-tests"
	fi
	local description="$GB_REPO_DESCRIPTION"
	if [ -z "$description" ]; then
		description="ALT Linux $label"
	fi
	local date_s="$(date +%s)"
	local arch="$APT_TEST_USED_ARCH"
	local comps="${label}"

	if [ -n "$repo_date" ] ; then
		date_s="$repo_date"
	fi

	local archive="$GB_REPO_ARCHIVE"
	if [ -z "$archive" ]; then
		archive="$description"
	fi
	local codename="$GB_REPO_CODENAME"
	if [ -z "$codename" ]; then
		codename="$date_s"
	fi
	local origin="$GB_REPO_ORIGIN"
	if [ -z "$origin" ]; then
		origin="ALT Linux Team"
	fi
	local suite="$GB_REPO_SUITE"
	if [ -z "$suite" ]; then
		suite="$label"
	fi
	local version="$GB_REPO_VERSION"
	if [ -z "$version" ]; then
		version="$date_s"
	fi

	# The compression opts can be forced from the environment.
	local compression_opts="$APT_TEST_BASEDIR_COMPRESSION_OPTS"
	if [ -z "$compression_opts" ]; then
		# By default, choose minimal required compressions
		# for the chosen method to work without errors.
		# (FIXME: fix APT so that the used compression doesn't matter!)
		case "$APT_TEST_METHOD" in

			http*|copy) compression_opts='--no-bz2 --xz'
			       REPO_COMPR_EXT=.xz
			       ;;

			cdrom*) compression_opts='--bz2 --no-xz'
				REPO_COMPR_EXT=.bz2
				;;

			*) compression_opts='--no-bz2 --no-xz'
			   REPO_COMPR_EXT=
			   ;;

		esac
	fi

	if [ -n "$orig_dir" ]; then
		# initial generation
		rm -rf "$repo_dir"
		for dir in ${arch} noarch ; do
			mkdir -p "${repo_dir}/${dir}/RPMS.${label}"
			mkdir -p "${repo_dir}/${dir}/base"
			stat "${orig_dir}/${dir}/"*.rpm &>/dev/null &&
				cp "${orig_dir}/${dir}/"*.rpm "${repo_dir}/${dir}/RPMS.${label}"/
		done
	fi

	mkdir -p "${TMPWORKINGDIRECTORY}/cache"

	for dir in ${arch} noarch ; do
		genbasedir \
			--cachedir="${TMPWORKINGDIRECTORY}/cache" \
			--architecture="$dir" \
			--architectures="$dir" \
			--archive="$archive" \
			--codename="$codename" \
			--description="$description" \
			--label="$label" \
			--origin="$origin" \
			--suite="$suite" \
			--version="$version" \
			--topdir="$repo_dir" \
			--changelog-since='@1' \
			--flat --no-oldhashfile $compression_opts --mapi \
			$APT_TEST_GENBASEDIR_OPTS \
			$dir $comps

		if [ -n "$repo_date" ] ; then
			touch -t $(date --date=@${repo_date} '+%Y%m%d%H%M.%S') \
				  "${repo_dir}/${dir}/base/"*
		fi
	done
}

# Input:
# * args ORIG_DIR [REPO_DATE]
# * files in $ORIG_DIR
# * global var APT_TEST_METHOD
#
# Output/effects:
# * server configured to give the data to apt
#   (or other actions, according to $APT_TEST_METHOD)
# * global var REPO_STORAGE -- where we physically store the data given to apt
#   (it will be set depending on $APT_TEST_METHOD)
# * files in $REPO_STORAGE
# * global vars NOARCH_DISTRO, MYARCH_DISTRO, DISTRO_COMPONENT
#   -- the paths under $REPO_STORAGE or $APT_SOURCES_URI; the final part
#   specified in sources.list after the URI with the component/"label"
# * server started to give the data to apt
#   (or other actions, according to $APT_TEST_METHOD)
# * global vars APT_SOURCES_SITE, APT_SOURCES_URI -- how apt sees its source repo:
#   - APT_SOURCES_SITE is shown as "Site:" by apt-cache dump
#   - APT_SOURCES_URI IS used in sources.list and to identify files in APT_LISTS_DIR
# * sources.list configured accordingly
#
# (The global vars are convenient for other scripts to refer to these values.)
generaterepository_and_switch_sources() {
	local -r ORIG_DIR="$1"
	local -r REPO_DATE="${2-}"

	# Configure the server to give the data to apt
	# (or other actions, according to $APT_TEST_METHOD).
	#
	# And set REPO_STORAGE accordingly.
	case "$APT_TEST_METHOD" in
	    http*)
		local -a nginxsetup_opts=()
		;;
	esac
	case "$APT_TEST_METHOD" in
	    https*)
	        nginxsetup_opts=("${nginxsetup_opts[@]}" https)
		;;
	esac
	case "$APT_TEST_METHOD" in
	    http*_localhost6*)
	        nginxsetup_opts=("${nginxsetup_opts[@]}" ipv6)
		;;
	esac
	case "$APT_TEST_METHOD" in
	    http*_proxy*)
	        nginxsetup_opts=("${nginxsetup_opts[@]}" only1ip)
		;;
	esac
	case "$APT_TEST_METHOD" in
	    http*_proxy*)
		local -a tinyproxysetup_opts=()

		case "$APT_TEST_METHOD" in
		    http*_proxy6*)
			tinyproxysetup_opts=("${tinyproxysetup_opts[@]}" ipv6)
			;;
		esac
		case "$APT_TEST_METHOD" in
		    http*_authproxy*)
			tinyproxysetup_opts=("${tinyproxysetup_opts[@]}" auth)
			;;
		esac

		tinyproxysetup "${tinyproxysetup_opts[@]}"
		;;
	esac

	case "$APT_TEST_METHOD" in

		file|copy)
			REPO_STORAGE="$TMPWORKINGDIRECTORY/usr/src/RPM/REPO"
			;;

		http*)
			nginxsetup "${nginxsetup_opts[@]}"
			REPO_STORAGE="$NGINX_STORAGE"
			;;

		cdrom|cdrom_*)
			REPO_STORAGE="$MOCKUP_CDROM_STORAGE"
			;;

		*)
			msgdie 'unknown $APT_TEST_METHOD'
			;;

	esac

	# Put the files into $REPO_STORAGE.
	#
	# Expose the paths used under it.
	generaterepository "$ORIG_DIR" "$REPO_STORAGE" "$REPO_DATE"
	NOARCH_DISTRO=noarch
	MYARCH_DISTRO="$APT_TEST_USED_ARCH"
	DISTRO_COMPONENT=apt-tests

	# Additional configuration for the server or other methods.

	case "$APT_TEST_METHOD" in
		cdrom|cdrom_*)
			mkdir "$MOCKUP_CDROM_STORAGE"/.disk
			# trailing whitespace here might be misinterpreted
			echo "Distribution $REPO_DATE Disk" >"$MOCKUP_CDROM_STORAGE"/.disk/info

			# DEBUG: Let's have an idea of the FS tree there:
			#ls -laR "$MOCKUP_CDROM_STORAGE"
			;;
	esac

	case "$APT_TEST_METHOD" in
	    cdrom_missing_release)
		find "$MOCKUP_CDROM_STORAGE"/*/base -name 'release*' -print -delete
		;;
	esac

	case "$APT_TEST_METHOD" in
		https|https_*)
			# generate key
			case "$APT_TEST_METHOD" in
				https_invalid_cert_hostname)
					local -r cert_hostname=wrong-"$NGINX_HOST"
					;;
				*)
					local -r cert_hostname="$NGINX_HOST"
					;;
			esac
			mk_intermediate cert/nginx@"$cert_hostname"/crt \
				ssl_keygen

			cp -f "$APT_TEST_INTERMEDIATES"/cert/nginx@"$cert_hostname"/key -T $NGINX_KEY
			cp -f "$APT_TEST_INTERMEDIATES"/cert/nginx@"$cert_hostname"/crt -T $NGINX_CRT

			# add cert to apt's config.
			# (Pinning is a separate feature to test.)
			cat >| $TMPWORKINGDIRECTORY/rootdir/etc/apt/apt.conf.d/80https.conf <<- END
			Acquire::https::CaInfo	"$NGINX_CRT";
			END
			;;
	esac

	case "$APT_TEST_METHOD" in
	    http*_proxy*)
		if [ -z "${TINYPROXY_USER-}" ]; then
		    TINYPROXY_AUTH=
		else
		    TINYPROXY_AUTH="$(encode_auth_in_uri "$TINYPROXY_USER")":"$(encode_auth_in_uri "$TINYPROXY_PASS")"@
		fi
		# The prefix http:// seems to be required by apt.
		case "$APT_TEST_METHOD" in
		    http*_numerproxy*)
			http_proxy="http://$TINYPROXY_AUTH$TINYPROXY_IP:$TINYPROXY_PORT"
			;;
		    *)
			http_proxy="http://$TINYPROXY_AUTH$TINYPROXY_HOST:$TINYPROXY_PORT"
			;;
		esac
		export http_proxy

		;;
	esac

	case "$APT_TEST_METHOD" in
	    https*_pinned*)
		cat >| "$APT_TEST_PINNING_CONF" <<- END
		Acquire::https::PinnedCert	"$NGINX_CRT";
		END

		;;
	esac

	case "$APT_TEST_METHOD" in
		*_dbgconn*)
		    mkdir -p "$APT_DEBUG_CONNECT_DIR"
		    {
			printf 'Debug::Connect "%s";\n' "$APT_DEBUG_CONNECT_DIR"
			echo 'Debug::Acquire::http "yes";'
		    } >$TMPWORKINGDIRECTORY/rootdir/etc/apt/apt.conf.d/81dbg-conn.conf
		    addtrap 'prefix' \
			    '[ "$EXIT_CODE" = 0 ] || print_debug_connect >&6;'
		    ;;
	esac

	# Start the server.
	case "$APT_TEST_METHOD" in
		http|http_*|https|https_*)
			nginxrestart
			;;
	esac

	# Start the proxy.
	case "$APT_TEST_METHOD" in
		http*_proxy*)
		    tinyproxyrestart
		    ;;
	esac

	# Overwrite old sources.list.
	#
	# And set APT_SOURCES_SITE, APT_SOURCES_URI accordingly.
	case "$APT_TEST_METHOD" in

		cdrom|cdrom_*)
			# overwrite old sources.list
			echo >| rootdir/etc/apt/sources.list
			aptcdrom add >&2 <<<$'\n'

			APT_SOURCES_SITE="$(<"$MOCKUP_CDROM_STORAGE"/.disk/info)"
			APT_SOURCES_URI="$APT_SOURCES_SITE"
			;;

		file|copy)
			APT_SOURCES_SITE=''
			APT_SOURCES_URI="$APT_SOURCES_SITE$REPO_STORAGE"

			cat >| rootdir/etc/apt/sources.list <<- END
				rpm $APT_TEST_METHOD://$APT_SOURCES_URI $MYARCH_DISTRO $DISTRO_COMPONENT
				rpm $APT_TEST_METHOD://$APT_SOURCES_URI $NOARCH_DISTRO $DISTRO_COMPONENT
			END
			;;

		http|http_*|https|https_*)
			case "$APT_TEST_METHOD" in
			    http*_numeric*)
				APT_SOURCES_SITE="$NGINX_IP"
				;;
			    *)
				APT_SOURCES_SITE="$NGINX_HOST"
				;;
			esac
			APT_SOURCES_URI="$APT_SOURCES_SITE:$NGINX_PORT"

			cat >| rootdir/etc/apt/sources.list <<- END
				rpm ${APT_TEST_METHOD%%_*}://$APT_SOURCES_URI/ $MYARCH_DISTRO $DISTRO_COMPONENT
				rpm ${APT_TEST_METHOD%%_*}://$APT_SOURCES_URI/ $NOARCH_DISTRO $DISTRO_COMPONENT
			END

			;;

		*)
			msgdie 'unknown $APT_TEST_METHOD'
			;;

	esac

	# debug failed downloads
	addtrap 'prefix' \
		'[ "$EXIT_CODE" = 0 ] || print_failed_lists >&6;'

	# Here we delete the braces in [::1]:8080 to match apt's filenames.
	NOARCH_PKGLIST_URI="${APT_SOURCES_URI//[][]}/$NOARCH_DISTRO/base/pkglist.$DISTRO_COMPONENT"
	MYARCH_PKGLIST_URI="${APT_SOURCES_URI//[][]}/$MYARCH_DISTRO/base/pkglist.$DISTRO_COMPONENT"
	local encoded_noarch_pkglist_uri encoded_myarch_pkglist_uri
	encoded_noarch_pkglist_uri="$(encode_uri_for_apt_lists "$NOARCH_PKGLIST_URI")"
	encoded_myarch_pkglist_uri=$(encode_uri_for_apt_lists "$MYARCH_PKGLIST_URI")
	local -r encoded_noarch_pkglist_uri encoded_myarch_pkglist_uri
	APT_FETCHED_NOARCH_PKGLIST="$APT_LISTS_DIR/$encoded_noarch_pkglist_uri"
	APT_FETCHED_MYARCH_PKGLIST="$APT_LISTS_DIR/$encoded_myarch_pkglist_uri"
	APT_FETCHED_PARTIAL_NOARCH_PKGLIST="$APT_LISTS_DIR/partial/$encoded_noarch_pkglist_uri"
	APT_FETCHED_PARTIAL_MYARCH_PKGLIST="$APT_LISTS_DIR/partial/$encoded_myarch_pkglist_uri"
}

readonly ALLOWED_FROM=127.0.0.2
# FIXME: use an address different from localhost6 (for testing
# proxy-only connections; now we should skip that part of the tests);
# consider RFC 4193 addresses as a solution (https://serverfault.com/a/935274/68972).
readonly ALLOWED_FROM6=::1

# Output/effects:
# * global vars representing the actual configuration of the server
#   (convenient for use in other scripts):
#   TINYPROXY_HOST, TINYPROXY_IP, TINYPROXY_PORT, TINYPROXY_PIDFILE;
#   if auth, also: TINYPROXY_USER, TINYPROXY_PASS
# * the server configured, ready for tinyproxyrestart()
tinyproxysetup() {
	local opt ipv6= auth=
	for opt; do
	    case "$opt" in

		ipv6)
		    ipv6=yes
		    ;;

		auth)
		    auth=yes
		    ;;

		*)
		    msgdie "unknown tinyproxysetup opt: $opt"
		    ;;

	    esac
	done
	local -r ipv6
	local -r auth

	mkdir -p $TMPWORKINGDIRECTORY/tinyproxy

	if [ -z "$ipv6" ]; then
            TINYPROXY_HOST=localhost
	    TINYPROXY_IP=127.0.0.1
	else
	    TINYPROXY_HOST=localhost6
	    TINYPROXY_IP=[::1]
	fi
        TINYPROXY_PORT=$((8580+${PARALLEL_SLOT:-0}))
	if [ -n "$auth" ]; then
	    # A colon (:) is prohibited (according to RFC2617 & RFC7617).
	    TINYPROXY_USER='John my@user name'
	    # https://bugzilla.altlinux.org/38277 , but tinyproxy
	    # doesn't allow to set a password with @ (TODO: report!),
	    # so we test with a colon (:) here and an @ in the
	    # username. This would not have evoked the actual problem
	    # on all code paths (and test cases: env var or config).
	    TINYPROXY_PASS='P:ss w0rd'
	fi
        TINYPROXY_PIDFILE=$TMPWORKINGDIRECTORY/tinyproxy/tinyproxy.pid
	TINYPROXYLOG=$TMPWORKINGDIRECTORY/tinyproxy/tinyproxy.log

	cat >| $TMPWORKINGDIRECTORY/tinyproxy/tinyproxy.conf << ENDCONFIG
PidFile "$TINYPROXY_PIDFILE"
Timeout 70
MaxClients 2

Port $TINYPROXY_PORT
# localhost (cannot be given by name)
Listen ${TINYPROXY_IP//[][]}

Bind $ALLOWED_FROM
Bind $ALLOWED_FROM6

# no other way to see the log than in a file; daemon cannot be made to use stdout:
# https://github.com/tinyproxy/tinyproxy/pull/122/commits/894a0aab3babfbe7d68fbc446ba50a848413992b#diff-46b881d73b3e6407e73cdcc59cdaf1de4bb5a0122acf867cb88bdd28ca34ca45R73
LogFile "$TINYPROXYLOG"

# LogLevel: Warning
#
# Set the logging level. Allowed settings are:
#	Critical	(least verbose)
#	Error
#	Warning
#	Notice
#	Connect		(to log connections without Info's noise)
#	Info		(most verbose)
#
# The LogLevel logs from the set level and above. For example, if the
# LogLevel was set to Warning, then all log messages from Warning to
# Critical would be output, but Notice and below would be suppressed.
#
LogLevel Connect

${auth:+
# No double quotes can be used. It can split wrongly on a space in the password.
BasicAuth $TINYPROXY_USER $TINYPROXY_PASS
}

ENDCONFIG
}

tinyproxyrestart() {

	! [ -s "$TINYPROXY_PIDFILE" ] || {
	    echo 'Stopping tinyproxy with a signal (before a restart).'
	    local -r old_pid="$(cat "$TINYPROXY_PIDFILE")"
	    kill "$old_pid"
	    # we can't "wait" for pid, because the daemon is not a child
	    wait_for_nofile "$TINYPROXY_PIDFILE" 5000 ||
		kill -KILL "$old_pid" &>/dev/null ||:
	    wait_until "! kill -0 $old_pid &>/dev/null" 5000 ||
		msgdie 'Timeout, could not kill the old tinyproxy'
	} &>>"$TINYPROXYLOG"
	rm -f "$TINYPROXY_PIDFILE"

	# The wait_for_file workaround is not needed if tinyproxy fully conforms
	# to the "traditional UNIX forking daemon" behavior described
	# in systemd.service(5); we don't know whether it does. (Nginx doesn't.)
	/usr/bin/tinyproxy -c $TMPWORKINGDIRECTORY/tinyproxy/tinyproxy.conf &&
		wait_for_file "$TINYPROXY_PIDFILE" &&
		local -r TINYPROXYPID="$(cat "$TINYPROXY_PIDFILE" 2>>"$TINYPROXYLOG")" &&
		[ -n "$TINYPROXYPID" ] ||
			{ { echo TINYPROXYLOG:; cat "$TINYPROXYLOG"; } >&6 ||:
			  msgdie 'tinyproxy failed to start normally'
			}

	# If we have started it one more time, the old trap would output the same log
	# one more time (FIXME: use unique files for each instance), and killing
	# would output an error 'No such process', but that's not a problem.
	addtrap 'prefix' \
			'kill -TERM %q ||:; [ "$EXIT_CODE" = 0 ] || if [ -e %q ]; then echo TINYPROXYLOG:; cat %q ||:; fi >&6;' \
			"$TINYPROXYPID" \
			"$TINYPROXYLOG" \
			"$TINYPROXYLOG"
}

# Output/effects:
# * global vars representing the actual configuration of the server
#   (convenient for use in other scripts):
#   NGINX_HOST, NGINX_IP, NGINX_PORT, NGINX_STORAGE, NGINX_PIDFILE;
#   if https, also: NGINX_KEY, NGINX_CRT;
# * the server configured, ready for nginxrestart()
#   (if https: configured & ready except for the key)
nginxsetup() {
	local opt https= only1ip= ipv6=
	for opt; do
	    case "$opt" in

		https)
		    https=yes
		    ;;

		only1ip)
		    only1ip=yes
		    ;;

		ipv6)
		    ipv6=yes
		    ;;

		*)
		    msgdie "unknown nginxsetup opt: $opt"
		    ;;

	    esac
	done
	local -r https
	local -r only1ip
	local -r ipv6

	mkdir -p $TMPWORKINGDIRECTORY/nginx
	mkdir -p $TMPWORKINGDIRECTORY/nginx/tmp

	if [ -z "$ipv6" ]; then
	    NGINX_HOST=localhost
	    NGINX_IP=127.0.0.1
	else
	    NGINX_HOST=localhost6
	    NGINX_IP=[::1]
	fi
	NGINX_PORT=$((8080+${PARALLEL_SLOT:-0}))
	if [ -n "$https" ]; then
	    NGINX_KEY=$TMPWORKINGDIRECTORY/nginx/cert.key
	    NGINX_CRT=$TMPWORKINGDIRECTORY/nginx/cert.crt
	fi
	NGINX_STORAGE=$TMPWORKINGDIRECTORY/nginx/repo
	NGINX_PIDFILE=$TMPWORKINGDIRECTORY/nginx/nginx.pid

	cat >| $TMPWORKINGDIRECTORY/nginx/nginx.conf << ENDCONFIG
error_log stderr;
worker_processes 1;
pid $NGINX_PIDFILE;

events {
	worker_connections  1024;
}

http {
	client_body_temp_path $TMPWORKINGDIRECTORY/nginx/tmp/client_body;
	fastcgi_temp_path $TMPWORKINGDIRECTORY/nginx/tmp/fastcgi_temp;
	proxy_temp_path $TMPWORKINGDIRECTORY/nginx/tmp/proxy_temp;
	scgi_temp_path $TMPWORKINGDIRECTORY/nginx/tmp/scgi_temp;
	uwsgi_temp_path $TMPWORKINGDIRECTORY/nginx/tmp/uwsgi_temp;

	include             /etc/nginx/mime.types;
	default_type        application/octet-stream;

	sendfile off;

	keepalive_timeout   65;

	ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
	ssl_prefer_server_ciphers on;
	access_log stderr;
	error_log stderr;

	server {
		listen ${ipv6:+[::]:}$NGINX_PORT ${https:+ssl};
		server_name $NGINX_HOST localhost${ipv6:+6}.localdomain;

	${https:+
		ssl_certificate	$NGINX_CRT;
		ssl_certificate_key	$NGINX_KEY;

		ssl_session_cache	builtin:1000	shared:SSL:10m;
		ssl_protocols TLSv1.2;
		ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4;
		ssl_prefer_server_ciphers on;
	}

		location / {
			root $NGINX_STORAGE;
			autoindex on;

		${only1ip:+
			allow $ALLOWED_FROM;
			allow $ALLOWED_FROM6;
			deny all;
		}

		}
	}
}
ENDCONFIG
}

nginxrestart() {
	local -r NGINXLOG=$TMPWORKINGDIRECTORY/nginx/process-stderr.log

	! [ -s "$NGINX_PIDFILE" ] || {
	    echo 'Stopping nginx with a signal (before a restart).'
	    local -r old_pid="$(cat "$NGINX_PIDFILE")"
	    kill "$old_pid"
	    # we can't "wait" for pid, because the daemon is not a child
	    wait_for_nofile "$NGINX_PIDFILE" 5000 ||
		kill -KILL "$old_pid" &>/dev/null ||:
	    wait_until "! kill -0 $old_pid &>/dev/null" 5000 ||
		msgdie 'Timeout, could not kill the old nginx'
	} &>>"$NGINXLOG"
	rm -f "$NGINX_PIDFILE"

	# The wait_for_file workaround would not be needed if nginx fully conformed
	# to the "traditional UNIX forking daemon" behavior described
	# in systemd.service(5); unfortunately for us, the upstream hasn't "fixed"
	# this race[1], although a patch for synchronizing the exit of the parent
	# with the creation of the pidfile is available[2].
	# [1]: https://trac.nginx.org/nginx/ticket/1897
	# [2]: http://mailman.nginx.org/pipermail/nginx-ru/2017-November/060628.html
	/usr/sbin/nginx -c $TMPWORKINGDIRECTORY/nginx/nginx.conf -p $TMPWORKINGDIRECTORY &>>"$NGINXLOG" &&
		wait_for_file "$NGINX_PIDFILE" &&
		local -r NGINXPID="$(cat "$NGINX_PIDFILE" 2>>"$NGINXLOG")" &&
		[ -n "$NGINXPID" ] ||
			{ { echo NGINXLOG:; cat "$NGINXLOG"; } >&6 ||:
			  msgdie 'nginx failed to start normally'
			}

	# If we have started it one more time, the old trap would output the same log
	# one more time (FIXME: use unique files for each instance), and killing
	# would output an error 'No such process', but that's not a problem.
	addtrap 'prefix' \
			'kill -TERM %q ||:; [ "$EXIT_CODE" = 0 ] || if [ -e %q ]; then echo NGINXLOG:; cat %q ||:; fi >&6;' \
			"$NGINXPID" \
			"$NGINXLOG" \
			"$NGINXLOG"
}

# Faking pkglist or its cksum (in the release file).
#
# There are two ways to go (so that there is a mismatch with
# the checksum in the release file):
#
# 1. Faking the pkglist itself. It is more realistic, but less reliable as
# to understanding the cause of apt's failure: whether it's just the cksum or
# another error in the format of the pkglist.
#
# To overcome the issue with the format, one could generate another real repo,
# and copy the pkglist from there. But then it's the size that could mismatch...
#
# And even a simple recompression of a minimally changed pkglist
# quite probably leads to a size mismatch. So, the test
# may seemingly pass because of the other reason for the mismatch.
#
# 2. Another way could be to edit the cksum in the release file instead.
# (This way is simpler, since we'd anyway have to edit the size
# of the compressed fake pkglist.)

# Input:
# * args CMD [ARG..] -- stream editor
# * global vars REPO_STORAGE, NOARCH_DISTRO
# * file $REPO_STORAGE/$NOARCH_DISTRO/base/release
#
# Output/effects:
# * the file is edited; its time is unchanged
edit_repo_noarch_release() {
	local -r repo_noarch_release="$REPO_STORAGE/$NOARCH_DISTRO/base/release"
	mv "$repo_noarch_release"{,.orig}

	"$@" \
	    >"$repo_noarch_release" \
	    <"$repo_noarch_release".orig

	if [ $MSGLEVEL -gt 3 ]; then # info
		echo "Edited $repo_noarch_release:"
		diff -u --text "$repo_noarch_release"{.orig,} && echo '(no diff)' ||:
	fi >&6

	touch -r "$repo_noarch_release".orig -- "$repo_noarch_release"
	rm -- "$repo_noarch_release".orig
}

# Here is an implementation of way 1:

# Input:
# * args CMD [ARG..] -- stream editor (for the pkglist)
# * global vars REPO_STORAGE, NOARCH_DISTRO, DISTRO_COMPONENT, REPO_COMPR_EXT
# * files $REPO_STORAGE/$NOARCH_DISTRO/base/{pkglist.$DISTRO_COMPONENT,release}
#   and its compressed counterpart (if any)
#
# Output/effects:
# * the file (and its compressed counterpart) is edited; its time is unchanged
# * the size of the compressed counterpart in the release file is adjusted; its time is unchanged
fake_repo_noarch_pkglist() {
	local -r repo_noarch_pkglist="$REPO_STORAGE/$NOARCH_DISTRO/base/pkglist.$DISTRO_COMPONENT"
	mv "$repo_noarch_pkglist"{,.orig}
	rm -f "$repo_noarch_pkglist$REPO_COMPR_EXT"

	"$@" \
	    >"$repo_noarch_pkglist" \
	    <"$repo_noarch_pkglist".orig

	if [ $MSGLEVEL -gt 3 ]; then # info
		echo "Edited $repo_noarch_pkglist:"
		diff --report-identical-files --brief "$repo_noarch_pkglist"{.orig,} ||:
	fi >&6

	touch -r "$repo_noarch_pkglist".orig -- "$repo_noarch_pkglist"

	if [ -n "$REPO_COMPR_EXT" ]; then

	    case "$REPO_COMPR_EXT" in
		.xz) xz --keep -- "$repo_noarch_pkglist"
		     ;;
		.bz2) bzip2 --keep -- "$repo_noarch_pkglist"
		      ;;
	    esac
	    touch -r "$repo_noarch_pkglist".orig -- "$repo_noarch_pkglist$REPO_COMPR_EXT"

	    # Now, we have to edit the size of the compressed fake pkglist.
	    local new_size
	    new_size="$(stat --format '%s' -- "$repo_noarch_pkglist$REPO_COMPR_EXT")"
	    local -r new_size
	    # FIXME: the regex is not quite a regex below
	    edit_repo_noarch_release \
		sed -Ee "s:^( [^ ]* )[^ ]*( base/pkglist\.$DISTRO_COMPONENT$REPO_COMPR_EXT):\1$new_size\2:"
	fi

	rm -- "$repo_noarch_pkglist".orig
}

# Here is an implementation of way 2:

# a helper function
bad_cksum() {
	local -r cksum_type="$1"; shift

	case "$cksum_type" in

	    MD5Sum)
	        md5sum /dev/null
		;;

	    SHA1)
	        sha1sum /dev/null
		;;

	    SHA256)
	        sha256sum /dev/null
		;;

	    BLAKE2b)
	        b2sum /dev/null
		;;

	    *)
		msgdie "Unknown cksum_type $cksum_type"
		;;

	esac | cut -d' ' -f1
}

# Input:
# * args: cksum_type CMD [ARG..] -- stream editor
# * global vars REPO_STORAGE, NOARCH_DISTRO
# * file $REPO_STORAGE/$NOARCH_DISTRO/base/release
#
# Output/effects:
# * the file is edited; its time is unchanged
fake_repo_noarch_pkglist_cksum() {
	local -r cksum_type="$1"; shift

	local a_bad_cksum
	a_bad_cksum="$(bad_cksum "$cksum_type")"
	local -r a_bad_cksum

	edit_repo_noarch_release \
	    sed -Ee "/^$cksum_type:/,/^[^ ]/ { /pkglist/ s:^ [^ ]* : $a_bad_cksum :; }"
}

# Input:
# * args: cksum_type CMD [ARG..] -- stream editor
# * global vars REPO_STORAGE, NOARCH_DISTRO
# * file $REPO_STORAGE/$NOARCH_DISTRO/base/release
#
# Output/effects:
# * the file is edited; its time is unchanged
fake_repo_noarch_pkglist_size() {
	local a_bad_size
	# a big number that fits into signed 64-bit integer, but not narrower
	printf -v a_bad_size '%d' 0x7edcba9876543210
	local -r a_bad_size

	edit_repo_noarch_release \
	    sed -Ee "/pkglist/ s:^( [^ ]*) [^ ]* :\1 $a_bad_size :"
}

# Input:
# * args NAME CMD [ARG..] -- the name of the pkg and a stream editor (for the pkg file)
# * global vars REPO_STORAGE, NOARCH_DISTRO, DISTRO_COMPONENT
# * files $REPO_STORAGE/$NOARCH_DISTRO/RPMS.$DISTRO_COMPONENT/NAME-*.rpm
#
# Output/effects:
# * the file is edited; its time is unchanged
fake_repo_noarch_rpm() {
	local -r name="$1"; shift

	local pkgfile
	pkgfile="$(builtpackagefile "$name")"
	pkgfile="${pkgfile##*/}"
	local -r pkgfile

	local -r repo_noarch_pkgfile="$REPO_STORAGE/$NOARCH_DISTRO/RPMS.$DISTRO_COMPONENT/$pkgfile"
	mv "$repo_noarch_pkgfile"{,.orig}

	"$@" \
	    >"$repo_noarch_pkgfile" \
	    <"$repo_noarch_pkgfile".orig

	if [ $MSGLEVEL -gt 3 ]; then # info
		echo "Edited $repo_noarch_pkgfile:"
		diff --report-identical-files --brief "$repo_noarch_pkgfile"{.orig,} ||:
	fi >&6

	touch -r "$repo_noarch_pkgfile".orig -- "$repo_noarch_pkgfile"

	rm -- "$repo_noarch_pkgfile".orig
}

# Input:
# * args: cksum_type NAME
# * global vars REPO_STORAGE
# * files in $TMPWORKINGDIRECTORY/cache/
#
# Output/effects:
# * files in $TMPWORKINGDIRECTORY/cache/ are edited
# * the index files in $REPO_STORAGE/*/base/ are regenerated
#   with fake cksums for the NAME rpm
#   (by calling generaterepository(), with all its effects)
fake_repo_rpm_cksum() {
	local -r cksum_type="$1"; shift
	local -r name="$1"; shift

	local pkgfile
	pkgfile="$(builtpackagefile "$name")"
	pkgfile="${pkgfile##*/}"
	local -r pkgfile

	local pkgfile_re
	set_regex_quote_str pkgfile_re "$pkgfile"
	local -r pkgfile_re

	local a_bad_cksum
	a_bad_cksum="$(bad_cksum "$cksum_type")"
	local -r a_bad_cksum

	cp -a "$TMPWORKINGDIRECTORY"/cache{,.orig} -T

	local cache_ext="$cksum_type"
	cache_ext="${cache_ext%Sum}"
	cache_ext="${cache_ext,,}"
	cache_ext="${cache_ext}cache"
	local -r cache_ext
	find "$TMPWORKINGDIRECTORY"/cache/ -name "*.$cache_ext" -print0 \
	    | xargs -0 --no-run-if-empty \
		    sed -Ee "/^$pkgfile_re / { s:^([^ ]*) [^ ]* :\1 $a_bad_cksum :; }" -i --

	if [ $MSGLEVEL -gt 3 ]; then # info
		echo "Edited *.$cache_ext"
		diff -u --text -Nr "$TMPWORKINGDIRECTORY"/cache{.orig,} && echo '(no diff)' ||:
	fi >&6
	rm -r -- "$TMPWORKINGDIRECTORY"/cache.orig

	# re-generate using the same rpms, but a cache with fake cksums
	generaterepository '' "$REPO_STORAGE"
}
