#!/bin/sh -ef
#
# Copyright (C) 2003-2008  Dmitry V. Levin <ldv@altlinux.org>
# Copyright (C) 2007  Alexey Tourbin <at@altlinux.org>
# 
# The contents cache functions for the hsh-initroot
#
# This file provides two top-level entry points: create_contents()
# and update_contents().  The former always rebuilds the contents
# form scratch, while the latter possibly reuses the cache.  After
# either of these functions is called, the contents_index_bin and
# contents_index_all global variables set to valid pathnames (however,
# if there is no single sources.list repo that provides contents_index
# file, the variables are set to null).
#
# 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.
#

# Quick protection against unintended filesystem changes.
[ -d "$cache_dir/contents" ] ||
	fatal "Internal error: cache/contents directory not set up."

# Find contents_index files corresponding to sources.list repositories.
contents_index_list=
init_contents_index_list()
{
	contents_index_list="$(awk '
		(NF==4 && $1=="rpm"){printf("%s/%s/base/contents_index\n",$2,$3)}
		(NF==5 && $1=="rpm"){printf("%s/%s/base/contents_index\n",$3,$4)}
		' <aptbox/etc/apt/sources.list |
			while IFS=: read uri_scheme f; do
				[ -r "$f" ] || continue
				printf %s\\n "$f"
			done)"
	if [ -n "$contents_index_list" ]; then
		verbose 'Initialized contents index list.'
	else
		verbose "No contents indices available, nothing to do."
		contents_index_bin=
		contents_index_all=
	fi
}

# Create list.new/ and index.new/ state.
create_contents_state()
{
	rm -rf "$cache_dir/contents/list.new"
	mkdir $verbose "$cache_dir/contents/list.new"
	printf %s\\n "$contents_index_list" >"$cache_dir/contents/list.new/index"

	rm -rf "$cache_dir/contents/index.new"
	mkdir $verbose "$cache_dir/contents/index.new"

	local f f_mangled
	while read f; do
		f_mangled="$(printf %s "$f" |sed -e s,/,_,g)"
		touch -r "$f" "$cache_dir/contents/index.new/$f_mangled" ||
			fatal "Failed to access contents index $f."
	done <"$cache_dir/contents/list.new/index"
}

# Compare {list,index}.new/ state with previous {list,index}/ state.
# Also check that contents_index files are present.
rebuild_contents_cache=
validate_contents_cache()
{
	if [ ! -s "$cache_dir/contents/contents_index_bin" ]; then
		verbose 'Missing contents_index_bin archive, invalidating contents cache.'
		rebuild_contents_cache=1
		return
	fi

	if [ ! -s "$cache_dir/contents/contents_index_all" ]; then
		verbose 'Missing contents_index_all archive, invalidating contents cache.'
		rebuild_contents_cache=1
		return
	fi

	if ! cmp -s "$cache_dir"/contents/list{.new,}/index; then
		verbose 'Index list changed, invalidating contents cache.'
		rebuild_contents_cache=1
		return
	fi

	local f f_mangled
	while read f; do
		f_mangled="$(printf %s "$f" |sed -e s,/,_,g)"
		if [ "$cache_dir/contents/index.new/$f_mangled" -nt \
		     "$cache_dir/contents/index/$f_mangled" -o \
		     "$cache_dir/contents/index.new/$f_mangled" -ot \
		     "$cache_dir/contents/index/$f_mangled" ]; then
			verbose "Index $f changed, invalidating contents cache."
			rebuild_contents_cache=1
			return
		fi
	done <"$cache_dir/contents/list.new/index"

	verbose "Contents cache is up to date."
	rebuild_contents_cache=
}

# Create contents_index_bin and contents_index_all files;
# also set up contents_index_bin contents_index_all variables.
contents_index_bin_dirs=/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/games:/etc
create_contents_data()
{
	rm -rf "$cache_dir/contents/tmp"
	mkdir "$cache_dir/contents/tmp"
	local contents="$cache_dir/contents/tmp/contents"

	local f
	while read f; do
		LC_ALL=C grep '^/' "$f" ||
			fatal "Failed to process contents index $f."
	done <"$cache_dir/contents/list.new/index" >"$contents"

	# sort (path,pkg)
	LC_COLLATE=C sort -k1,1 -k2,2 -u -o "$contents" "$contents"

	# (path,pkg) -> (pkg,path)
	awk '{print$2,$1}' "$contents" |
		# (pkg,path) -> (n_path,pkg0,path)
		uniq -c -f1 |
		# if (n_path > 1) -> (path,path) else -> (path,pkg0)
		awk '($1>1){$2=$3}{print$3"\t"$2}' >"$contents".sorted
	mv -f "$contents".sorted "$contents"
	[ -s "$contents" ] ||
		fatal "Failed to process contents indices."

	local dir
	for dir in $(IFS=:; echo $contents_index_bin_dirs); do
		LC_ALL=C grep ^"${dir%/}"/ "$contents" || [ $? -eq 1 ]
	done >"$contents.bin"
	[ -s "$contents.bin" ] ||
		fatal "Failed to generate non-empty contents_index_bin."

	contents_index_bin=chroot/.host/contents_index_bin
	contents_index_all=chroot/.host/contents_index_all
	mv "$contents.bin" "$contents_index_bin"
	mv "$contents" "$contents_index_all"
	chmod 644 "$contents_index_bin" "$contents_index_all"
	verbose 'Prepared contents index.'
	rm -rf "$cache_dir/contents/tmp"
}

# Copy file, use cp -l if possible
cp_l_file_from_to()
{
	local from="$1"; shift
	local to="$1"; shift
	local cp_args dev_from dev_to

	dev_from="$(stat -c '%d' -- "$from")"
	dev_to="$(stat -c '%d' -- "${to%/*}/")"
	[ "$dev_from" = "$dev_to" ] && cp_args=-l || cp_args=
	cp -f $cp_args $verbose -- "$from" "$to"
}

# Save data files from chroot/.host/ host to "$cache_dir"/contents/;
# also rotate {list,index}.new/ state.
archive_contents()
{
	rm -rf "$cache_dir"/contents/{index,list}
	cp_l_file_from_to chroot/.host/contents_index_bin "$cache_dir/contents/contents_index_bin"
	cp_l_file_from_to chroot/.host/contents_index_all "$cache_dir/contents/contents_index_all"
	mv $verbose "$cache_dir"/contents/index{.new,}
	mv $verbose "$cache_dir"/contents/list{.new,}
	verbose "Archived contents index."
}

# Copy data files from "$cache_dir"/contents/ to chroot/.host/.
# also set up contents_index_bin contents_index_all variables.
unpack_contents()
{
	contents_index_bin=chroot/.host/contents_index_bin
	contents_index_all=chroot/.host/contents_index_all
	cp_l_file_from_to "$cache_dir/contents/contents_index_bin" "$contents_index_bin"
	cp_l_file_from_to "$cache_dir/contents/contents_index_all" "$contents_index_all"
	verbose 'Unpacked contents index.'
}

# Entry point: do whatever is needed, always rebuilding contents from scratch.
create_contents()
{
	init_contents_index_list
	[ -n "$contents_index_list" ] || return 0
	create_contents_state
	create_contents_data
	archive_contents
}

# Entry point: do whatever is needed, possibly reusing the cache.
update_contents()
{
	init_contents_index_list
	[ -n "$contents_index_list" ] || return 0
	create_contents_state
	validate_contents_cache
	if [ -n "$rebuild_contents_cache" ]; then
		create_contents_data
		archive_contents
	else
		unpack_contents
	fi
}
