# Functions for installing systems
#
# Copyright (C) 2013-2015  Patrick McDermott
#
# This file is part of the ProteanOS Development Kit.
#
# The ProteanOS Development Kit 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 3 of the
# License, or (at your option) any later version.
#
# The ProteanOS Development Kit 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 the ProteanOS Development Kit.  If not, see
# <http://www.gnu.org/licenses/>.

install_deps=
install_urls=
install_md5sums=
install_sha256sums=
install_feed_url=

_usign_fingerprint()
{
	local key_data="${1}"
	shift 1
	local usign=

	if ${in_place} && ${USIGN_EMBEDDED}; then
		usign="${builddir}/3rdparty/usign/usign"
	else
		usign="${USIGN}"
	fi

	IFS="${LF}"
	if printf '%s\n' ${key_data} | "${usign}" -F -p -; then
		unset IFS
		return 0
	else
		unset IFS
		return 1
	fi
}

_install_deps_cb()
{
	local pkg="${1}"
	local deps="${2}"
	shift 2

	install_deps="${install_deps}${pkg}: ${deps}${LF}"

	return 0
}

_install_fname_cb()
{
	local pkg="${1}"
	local fname="${2}"
	shift 2

	install_urls="$(printf '%s\n%s %s/%s' "${install_urls}" \
		"${pkg}" "${install_feed_url}" "${fname}")"

	return 0
}

_install_md5sum_cb()
{
	local pkg="${1}"
	local md5sum="${2}"
	shift 2

	install_md5sums="$(printf '%s\n%s %s' "${install_md5sums}" \
		"${pkg}" "${md5sum}")"

	return 0
}

_install_sha256sum_cb()
{
	local pkg="${1}"
	local sha256sum="${2}"
	shift 2

	install_sha256sums="$(printf '%s\n%s %s' \
		"${install_sha256sums}" "${pkg}" "${sha256sum}")"

	return 0
}

install_find_pkgs()
{
	local mirror="${1}"
	local suite="${2}"
	local arch="${3}"
	local plat="${4}"
	local root="${5}"
	shift 5
	local opkg_conf_fd=
	local pkgs_fd=
	local type=
	local name=
	local base_url=
	local gzip=
	local cert_url=
	local root_key=
	local cert_fd=
	local dl_func=
	local pkgs=

	mkdir -p -- "${root}/etc/opkg" "${root}/$(profile_opkg_state_dir)/lists"
	if ! fopen "${root}/$(profile_opkg_conf_file)" 'w'; then
		return 1
	fi
	opkg_conf_fd=${FD}

	install_deps=
	install_urls=
	install_md5sums=
	install_sha256sums=

	if ! fopen "${root}/.prokit/packages" 'w'; then
		return 1
	fi
	pkgs_fd=${FD}

	if cert_url="$(profile_get_cert_url "${mirror}")"; then
		# This is a hack to generate a fake certificate with some valid
		# (but incorrect) key and the mirror-specific certificate URL to
		# make opkg-cert download and verify the actual certificate.
		mkdir -p -- "${root}/etc/opkg/keys/" "${root}/var/cache/opkg/"
		root_key="$(profile_get_root_key)"
		printf '%s\n' "${root_key}" \
			>"${root}/etc/opkg/keys/$(_usign_fingerprint \
				"${root_key}").root"
		if ! fopen "${root}/etc/opkg/keys/temp.cert" 'w'; then
			return 1
		fi
		cert_fd=${FD}
		printf '%s\n---\n' "${cert_url}" >&${cert_fd}
		IFS="${LF}"
		printf 'K: %s\n' ${root_key} >&${cert_fd}
		unset IFS
		printf '%s' '---' >&${cert_fd}
		fclose ${cert_fd}
		printf 'option check_signature 1\n\n' >&${opkg_conf_fd}
		dl_func=feed_download_cert
	else
		dl_func=feed_download
	fi

	while read -r type name base_url; do
		case "${type}" in
			'src')    gzip=false;;
			'src/gz') gzip=true;;
			*)        continue;;
		esac
		printf '%s %s %s\n' "${type}" "${name}" \
			"${base_url}"  >&${opkg_conf_fd}
		if ! ${dl_func} "${base_url}" \
			"${root}/$(profile_opkg_state_dir)/lists/${name}" \
				${gzip} "${root}"; then
			return 1
		fi
		install_feed_url="${base_url}"
		feed_find_pkgs \
			"${root}/$(profile_opkg_state_dir)/lists/${name}" \
			"$(profile_dep_fields)" profile_include_pkg \
			_install_deps_cb _install_fname_cb \
			_install_md5sum_cb _install_sha256sum_cb \
			>&${pkgs_fd}
	done <<-EOF
		$(profile_feeds "${mirror}" "${arch}" "${plat}" "${suite}")
		EOF

	printf '\ndest root /\n' >&${opkg_conf_fd}
	printf 'arch %s 1\n' 'all' "${arch}" 'src' >&${opkg_conf_fd}
	fclose ${opkg_conf_fd}
	fclose ${pkgs_fd}

	pkgs="$(resolve_deps "$(cat -- "${root}/.prokit/packages")" \
		"${install_deps}" | xargs printf '%s\n' | sort -u)"
	printf '%s\n' "${pkgs}" >"${root}/.prokit/packages"

	return 0
}

install_get_pkgs()
{
	local root="${1}"
	shift 1
	local status_fd=
	local errors=
	local pkg=
	local url=
	local md5sum=
	local sha256sum=
	local fname=
	local file=
	local control=
	local field=
	local printed=

	mkdir -p -- "${root}/var/cache/opkg/archives" "${root}/tmp/opkg" \
		"${root}/$(profile_opkg_state_dir)/info"

	if ! fopen "${root}/$(profile_opkg_state_dir)/status" 'w'; then
		return 1
	fi
	status_fd=${FD}

	errors=false

	for pkg in $(cat -- "${root}/.prokit/packages"); do
		info "$(get_msg 'install_downloading_pkg')" "${pkg}"
		url="$(printf '%s\n' "${install_urls}" | \
			sed -n "s/^${pkg} //p")"
		md5sum="$(printf '%s\n' "${install_md5sums}" | \
			sed -n "s/^${pkg} //p")"
		sha256sum="$(printf '%s\n' "${install_sha256sums}" | \
			sed -n "s/^${pkg} //p")"
		fname="var/cache/opkg/archives/${url##*/}"
		if ! ${WGET} -q -O "${root}/${fname}" -- "${url}"; then
			error "$(get_msg 'install_downloading_pkg_fail')"
			errors=true
			continue
		fi
		if [ "x${md5sum}" != 'x' ]; then
			if ! printf '%s  %s\n' \
					"${md5sum}" "${root}/${fname}" | \
					${MD5SUM} -c >/dev/null 2>&1; then
				error "$(get_msg 'install_checksum_fail')"
				errors=true
				continue
			fi
		fi
		if [ "x${sha256sum}" != 'x' ]; then
			if ! printf '%s  %s\n' \
					"${sha256sum}" "${root}/${fname}" | \
					${SHA256SUM} -c >/dev/null 2>&1; then
				error "$(get_msg 'install_checksum_fail')"
				errors=true
				continue
			fi
		fi

		info "$(get_msg 'install_unpacking_pkg')" "${pkg}"
		mkdir -- "${root}/tmp/opkg/${pkg}"
		(
			cd -- "${root}"
			tar -xzOf "${fname}" data.tar.gz \
				>"tmp/opkg/${pkg}/data.tar.gz"
			tar -xzf "tmp/opkg/${pkg}/data.tar.gz"
			cd -- "tmp/opkg/${pkg}"
			tar -xzOf "../../../${fname}" control.tar.gz | \
				tar -xz
		)
		tar -tzf "${root}/tmp/opkg/${pkg}/data.tar.gz" | \
			sed 's/^\.//' \
			>"${root}/$(profile_opkg_state_dir)/info/${pkg}.list"
		rm -f -- "${root}/tmp/opkg/${pkg}/data.tar.gz"
		for file in "${root}/tmp/opkg/${pkg}/"*; do
			mv -- "${file}" \
				"${root}/$(profile_opkg_state_dir \
					)/info/${pkg}.${file##*/}"
		done
		rmdir -- "${root}/tmp/opkg/${pkg}"

		# Write status file.
		control="${root}/$(profile_opkg_state_dir)/info/${pkg}.control"
		for field in Package Version Depends Recommends Suggests \
				Provides Replaces Conflicts; do
			grep -- "^${field}: " "${control}" >&${status_fd}
		done
		printf 'Status: install ok unpacked\n' >&${status_fd}
		for field in Essential Architecture; do
			grep -- "^${field}: " "${control}" >&${status_fd}
		done
		if [ -r "${root}/$(profile_opkg_state_dir \
				)/info/${pkg}.conffiles" ]; then
			printed=false
			while read -r file; do
				${printed} || printf 'Conffiles:\n' \
					>&${status_fd}
				printf ' %s %s\n' "${file}" "$(md5sum \
					"${root}/${file}" | cut -d' ' -f1)" \
					>&${status_fd}
				printed=true
			done <"${root}/$(profile_opkg_state_dir \
				)/info/${pkg}.conffiles"
		fi
		printf 'Installed-Time: %d\n\n' $(time) >&${status_fd}

		rm -f -- "${root}/${fname}"
	done

	rm -f -- "${root}/.prokit/packages"
	rmdir -- "${root}/.prokit"

	fclose ${status_fd}

	if ! ${errors}; then
		return 0
	else
		return 1
	fi
}

install_system()
{
	local mirror="${1}"
	local suite="${2}"
	local arch="${3}"
	local plat="${4}"
	local root="${5}"
	local foreign="${6}"
	shift 6

	if [ "x${arch}" = 'x' ]; then
		arch="$(profile_detect_arch)"
	fi
	if [ "x${plat}" = 'x' ]; then
		plat="$(profile_default_plat)"
	fi
	if [ "x${mirror}" = 'x' ]; then
		mirror="$(profile_select_mirror)"
	fi

	if ! profile_validate_archplat "${mirror}" "${arch}" "${plat}" \
			"${suite}"; then
		error "$(get_msg 'install_bad_archplat')" "${arch}" "${plat}"
		return 1
	fi

	info "$(get_msg 'install_selected_arch')" "${arch}"
	info "$(get_msg 'install_selected_plat')" "${plat}"
	info "$(get_msg 'install_selected_mirror')" "${mirror}"

	info "$(get_msg 'install_setting_up_chroot')"
	if [ -d "${root}" ] && ! dir_is_empty "${root}" 'lost+found'; then
		error "$(get_msg 'install_chroot_dir_exists')" "${root}"
		return 1
	fi
	if ! mkdir -p -- "${root}/.prokit" "${root}/prokit"; then
		error "$(get_msg 'install_mkdir_chroot_fail')" "${root}"
		return 1
	fi
	>"${root}/prokit/installing"

	info "$(get_msg 'install_find_pkgs')"
	if ! install_find_pkgs "${mirror}" "${suite}" "${arch}" "${plat}" \
			"${root}"; then
		return 1
	fi
	if ! install_get_pkgs "${root}"; then
		return 1
	fi

	info "$(get_msg 'install_configuring')"
	if ! ${foreign}; then
		profile_configure_system_native "${root}" "${arch}" "${plat}"
	else
		profile_configure_system_foreign "${root}" "${arch}" "${plat}"
	fi

	rm -f -- "${root}/prokit/installing"

	return 0
}