#! /bin/sh
#
# Modified by ALT Linux Team <apt@packages.altlinux.org>.
# Last change by Sviatoslav Sviridov <svd@altlinux.ru>.
# genbasedir was rewritten as mix of genbasedir scripts
# from Connectiva's apt-0.5.4cnc9 and ALT Linux Team's
# apt-0.3.19cnc55-alt9.
#
# The previous version from apt-0.5.4cnc9 was:
# $Id: genbasedir,v 1.6 2002/09/04 22:48:37 niemeyer Exp $
#
# This script generates the contents of the base/ directory, by creating
# the pkglists and the hash file. Update the components directory to contain
# the components of your repository.
#

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

topdir=

basedir=.
signature=
listonly=
hashonly=
partial=
newhashfile=1
oldhashfile=1
mapi=
bz2only=
updateinfo=
progress=
flat=
defaultkey=
topdir=
create=
verbose=
cachedir=
useful_files=

maybe_unchanged=
unchanged=1

# bloat is necessary for non-Conectiva distros, at least RH,
# because they use file dependencies with a non-predictable
# heuristic. So we can't strip-off paths that will probably
# never appear in dependencies.
bloat=
noscan=

# Global release file sections
origin="Unknown"
label="Unknown"
suite="Unknown"
codename="Unknown"
date="`date -R`"
architectures="Unknown"
description="Not Available"

# Component release file sections
archive="Unknown"
version="Unknown"
architecture="Unknown"
notautomatic="false"

changelog_since=

make_bz2=1
make_xz=1

Fatal()
{
	echo "${0##*/}: $*" >&2
	exit 1
}

Verbose()
{
	if [ -n "$verbose" ]; then
		echo "$@"
	fi
}

USAGE()
{
	cat >&2 <<EOF
Usage: ${0##*/} [<options>] <distribution> [<comp1> [<comp2> ...]]

Options:
   -s, --sign         Generate and sign hashfile
   --default-key=ID   Use ID as gnupg secret key
   --hashonly         Do hash stuff only
   --listonly         Generate pkglists/srclists and quit
   --partial          Update just some of the already existent components
   --newhashfile      Enable generation of new style hashfile
   --no-newhashfile   Disable generation of new style hashfile
   --oldhashfile      Enable generation of old style hashfile
   --no-oldhashfile   Disable generation of old style hashfile
   --bz2only          Generate only compressed lists
   --progress         Show progress bars for genpkglist/gensrclist;
                      implies --verbose
   -v, --verbose      Be more talkative
   --silent           Be less talkative (default)
   --topdir=dir       Top directory of repository
   --updateinfo=FILE  Update information file
   --mapi             List only those pkgs in srclist which generate
                      some binaries
   --flat             Use flat repository, where SRPMS and RPMS are in
                      the topdir (SRPMS are usually in 'topdir/..')
   --no-scan          Do not scan for useful files.
   --bloat            Do not strip the package file list. Needed for some
                      distributions that use non-automatically generated
                      file dependencies
   --useful-files=FILE
                      Read the list of useful files from FILE.
                      Do not strip these files from the package file list.
   --create           Create base directory if needed

   --origin=ORIGIN    Set "Origin" field in global release file
   --label=LABEL      Set "Label" field in global release file
   --suite=SUITE      Set "Suite" field in global release file
   --codename=CODENAME
                      Set "Codename" field in global release file
   --architectures=ARCHITECTURES
                      Set "Architectures" field in global release file
   --description=DESCRIPTION
                      Set "Description" field in global release file

   --archive=ARCHIVE  Set "Archive" field in component release file
   --version=VERSION  Set "Version" field in component release file
   --architecture=ARCHITECTURE
                      Set "Architecture" field in component release file
   --notautomatic=true|false  Set "NotAutomatic" field in component release file
   --cachedir=DIR     Use a custom md5sum cache directory for package list
   --changelog-since=DATE     Save package changelogs; copy changelog entries
                              newer than DATE, and also one preceding entry
   --maybe-unchanged  Skip the update if pkglist is unchanged.

   -h,--help          Show this help screen

Examples:

   ${0##*/} --topdir=/var/ftp/pub/distributions/ALTLinux/Sisyphus i586
   ${0##*/} --topdir=/var/ftp/pub/distributions/ALTLinux/Sisyphus i586 base kernel castle junior master contrib classic non-free
EOF
	[ -n "$1" ] && exit "$1" || exit
}

phashstuff()
{
    size=`wc -c <"$1"`
    md5=`md5sum "$1"|cut -f1 -d\  `
    echo " $md5 $size $2"
}

TEMP=`getopt -n $PROG -o vhs -l help,mapi,listonly,bz2only,hashonly,updateinfo:,bloat,no-scan,topdir:,sign,default-key:,progress,verbose,silent,oldhashfile,newhashfile,no-oldhashfile,no-newhashfile,partial,flat,create,origin:,label:,suite:,codename:,architectures:,description:,archive:,version:,architecture:,notautomatic:,cachedir:,useful-files:,changelog-since: \
	-l bz2,no-bz2,xz,no-xz,maybe-unchanged -- "$@"` || USAGE
eval set -- "$TEMP"

while :; do
	case "$1" in
		--listonly) shift; listonly=1
			;;
		--bz2only) shift; bz2only=1
			;;
		--hashonly) shift; hashonly=1
			;;
		-s|--sign) shift; signature=1
			;;
		--bloat) shift; bloat="--bloat"
			;;
		--no-scan) shift; noscan="--no-scan"
			;;
		--mapi) shift; mapi="--mapi"
			;;
		--updateinfo) shift; updateinfo="$1"; shift
			;;
		--default-key) shift; defaultkey="$1"; shift
			;;
		--topdir) shift; topdir="$1"; shift
			;;
		--flat) shift; flat="--flat"
			;;
		--progress) shift; progress="--progress"; verbose=1
			;;
		-v|--verbose) shift; verbose=1
			;;
		--silent) shift; verbose=
			;;
		--partial) shift; partial=1
			;;
		--oldhashfile) shift; oldhashfile=1
			;;
		--no-oldhashfile) shift; oldhashfile=
			;;
		--newhashfile) shift; newhashfile=1
			;;
		--no-newhashfile) shift; newhashfile=
			;;
		-h|--help) USAGE 0
			;;
		--create) shift; create=1
			;;
		--origin) shift; origin="$1"; shift
			;;
		--label) shift; label="$1"; shift
			;;
		--suite) shift; suite="$1"; shift
			;;
		--codename) shift; codename="$1"; shift
			;;
		--architectures) shift; architectures="$1"; shift;
			;;
		--description) shift; description="$1"; shift;
			;;
		--archive) shift; archive="$1"; shift;
			;;
		--version) shift; version="$1"; shift;
			;;
		--architecture) shift; architecture="$1"; shift;
			;;
		--notautomatic) shift; notautomatic="$1"; shift;
			;;
		--changelog-since) shift
			changelog_since=$(date -d "$1" +%s) &&
				[ "$changelog_since" -gt 0 ] ||
				Fatal "invalid --changelog-since date: $1"
			shift ;;
		--cachedir) shift; cachedir="$1"; shift;
			;;
		--useful-files) shift; useful_files="$1"; shift;
			;;
		--bz2) shift; make_bz2=1 ;;
		--no-bz2) shift; make_bz2= ;;
		--xz) shift; make_xz=1 ;;
		--no-xz) shift; make_xz= ;;
		--maybe-unchanged) shift; maybe_unchanged=1 ;;
		--) shift; break
			;;
		*) echo "$PROG: unrecognized option: $1" >&2; exit 1
			;;
	esac
done

topdir="`echo -n "$topdir" |tr -s /`"

[ -n "$topdir" ] || Fatal 'TOPDIR not specified.'

cd "$topdir" || Fatal "Invalid TOPDIR specified: $topdir"

# this will fix the path if it was relative
topdir=`pwd`

distro="$1"
shift

components="$*"

basedir_=base
oldbasedir_="`echo "$distro/$basedir_"|tr -s /`"
basedir="`echo "$topdir/$oldbasedir_"|tr -s /`"

pkglist_=$basedir_/pkglist
srclist_=$basedir_/srclist
release_=$basedir_/release
oldpkglist_=$oldbasedir_/pkglist
oldsrclist_=$oldbasedir_/srclist
oldrelease_=$oldbasedir_/release
pkglist=$basedir/pkglist
srclist=$basedir/srclist
release=$basedir/release

if [ -z "$flat" ]; then
	srctopdir=`cd $topdir/$distro/..; pwd`
else
	srctopdir=`cd $topdir/$distro; pwd`
fi

WORKDIR=
exit_handler()
{
	[ -z "$WORKDIR" ] || rm -rf -- "$WORKDIR"
	exit $1
}
trap 'exit_handler $?' EXIT
trap 'exit 143' HUP PIPE INT QUIT TERM
WORKDIR="$(mktemp -dt "$PROG.XXXXXXXXXX")" || exit

if [ ! -d "$basedir" ]; then
	if [ -n "$create" ]; then
		Verbose -n 'Creating base directory... '
		if ! mkdir -p "$basedir" >/dev/null 2>&1; then
			Fatal 'Unable to create base directory'
		fi
		Verbose 'done'
	else
		Fatal 'Base directory does not exist!'
	fi
fi

if [ -z "$components" ]; then
	# Try to guess components
	comps=$WORKDIR/components
	: >$comps
	for dir in $topdir/$distro/RPMS.* $srctopdir/SRPMS.*; do
		if [ -d $dir ]; then
			echo $dir | sed 's/.*\.//' >> $comps
		fi
	done
	components=`cat $comps|sort|uniq`
	components=`echo $components` # eat newlines
	rm -f $comps
fi

if [ -z "$components" ]; then
	Fatal 'No components found'
else
	Verbose "Components: $components"
fi

saved_list=

save_file()
{
	saved_list="$1"

	if [ -f "$saved_list" ]; then
		mv -f "$saved_list" "$saved_list.old"
	else
		saved_list=
	fi
}

compare_file()
{
	if [ -n "$saved_list" -a -f "$saved_list.old" ]; then
		if cmp -s "$saved_list.old" "$saved_list"; then
			mv -f "$saved_list.old" "$saved_list"
		else
			rm -f "$saved_list.old"
		fi
	fi
}

compress_file()
{
	local filesize halfsize pid pxz=

	rm -f "$1.xz" "$1.yz"
	if [ -n "$make_xz" ]; then
		filesize=$(stat -c '%s' "$1")
		if [ "$filesize" -ge $((4*8*1024*1024)) ]; then
			# The file is large enough (as compared to typical dictionary size).
			# Note that .xz files can be concatenated, so it's nice to have
			# 2-way parallel compression here.
			pxz=1
		fi
		if [ -n "$pxz" ]; then
			halfsize=$(($filesize/2))
			dd if="$1" bs="$halfsize" count=1 2>/dev/null |xz >"$1.xz" & pid=$!
			dd if="$1" bs="$halfsize"  skip=1 2>/dev/null |xz >"$1.yz"
			wait "$pid"
			cat "$1.yz" >>"$1.xz"
			rm -f "$1.yz"
		else
			xz -k "$1"
		fi &
	fi

	rm -f "$1.bz2"
	if [ -n "$make_bz2" ]; then
		if [ -n "$pxz" ]; then
			bzip2 -k "$1"
		else
			# Assuming non-parallel xz is expensive...
			nice bzip2 -k "$1"
		fi &
	fi
}

if [ -n "$cachedir" ]; then
	mkdir -p "$cachedir/genpkglist" "$cachedir/gensrclist"
fi

if [ -z "$hashonly" ]; then
	Verbose -n 'Processing packages...'

	for comp in $components; do

		SRCIDX_COMP="$WORKDIR/$comp"

		# pkglist
		if [ ! -d $topdir/$distro/RPMS.$comp ]; then
			# remove stale lists
			rm -f "$pkglist.$comp" "$pkglist.$comp".{bz2,xz}
			rm -f "$srclist.$comp" "$srclist.$comp".{bz2,xz}
			continue
		fi
		Verbose -n " RPMS.$comp"
		save_file "$pkglist.$comp"
		(cd "$basedir" &&
			genpkglist $progress $bloat $noscan --index "$SRCIDX_COMP" \
				${updateinfo:+--info "$updateinfo"} \
				${cachedir:+--cachedir "$cachedir"} \
				${useful_files:+--useful-files "$useful_files"} \
				${changelog_since:+--changelog-since "$changelog_since"} \
				"$topdir/$distro" "$comp")
		if [ $? -ne 0 ]; then
			Verbose
			Fatal 'Error executing genpkglist.'
		fi
		compare_file
		if [ -n "$maybe_unchanged" ]; then
			hash=$(phashstuff "$pkglist.$comp" "$pkglist_.$comp")
			if LC_ALL=C fgrep -qs -x "$hash" "$release"; then
				Verbose "$pkglist.$comp is unchanged"
				continue
			else
				Verbose "$pkglist.$comp is going to be changed despite --maybe-unchanged"
			fi
			unchanged=
		fi
		compress_file "$pkglist.$comp"

		# srclist
		if [ ! -d $srctopdir/SRPMS.$comp ]; then
			# remove stale lists
			rm -f "$srclist.$comp" "$srclist.$comp".{bz2,xz}
			continue
		fi
		save_file "$srclist.$comp"
		(cd "$basedir" &&
			gensrclist $progress $flat $mapi \
				${cachedir:+--cachedir "$cachedir"} \
				"$srctopdir" "$comp" "$SRCIDX_COMP")
		if [ $? -ne 0 ]; then
			Verbose
			Fatal 'Error executing gensrclist.'
		fi
		compare_file
		if [ -n "$maybe_unchanged" ]; then
			hash=$(phashstuff "$srclist.$comp" "$srclist_.$comp")
			if LC_ALL=C fgrep -qs -x "$hash" "$release"; then
				Verbose "$srclist.$comp is unchanged"
				continue
			else
				Verbose "$srclist.$comp is going to be changed despite --maybe-unchanged"
			fi
			unchanged=
		fi
		compress_file "$srclist.$comp"

	done
	Verbose ' done'

	Verbose -n 'Waiting for bzip2 and xz to finish...'
	wait
	Verbose ' done'
fi

remove_uncompressed()
{
	if [ -n "$bz2only" ]; then
		for comp in $components; do
			rm -f "$pkglist.$comp" "$srclist.$comp"
		done
	fi
}

if [ -n "$maybe_unchanged" ] && [ -n "$unchanged" ]; then
	remove_uncompressed
	Verbose "$release is unchanged"
	exit 0
fi

phash()
{
	if [ -f "$1" ]; then
		phashstuff "$1" "$2" >> "$3"
	fi
}

# Creating new style hashfile
if [ -n "$newhashfile" -a -z "$listonly" ]; then
	Verbose -n 'Creating component releases...'
	for comp in $components; do
		Verbose -n " $comp"
		save_file "$release.$comp"
		cat > "$release.$comp" <<- __EOF__
			Archive: $archive
			Component: $comp
			Version: $version
			Origin: $origin
			Label: $label
			Architecture: $architecture
			NotAutomatic: $notautomatic
		__EOF__
		compare_file
	done
	Verbose ' done'

	Verbose -n 'Updating global release file... '
	save_file "$release"
	if [ -f "$release" -a -n "$partial" ]; then
		sed -n -e "/^\$/q" \
			-e "s/^Date:.*\$/Date: $date/" \
			-e "p" "$release.old" > "$release.pre"
		for comp in $components; do
			sed -e "\#^ .* $pkglist_.$comp\(.bz2\)\?\$#d" \
			    -e "\#^ .* $srclist_.$comp\(.bz2\)\?\$#d" \
			    -e "\#^ .* $release_.$comp\(.bz2\)\?\$#d" \
			    -e "\#^ .* $pkglist_.$comp\(.xz\)\?\$#d" \
			    -e "\#^ .* $srclist_.$comp\(.xz\)\?\$#d" \
			    -e "\#^ .* $release_.$comp\(.xz\)\?\$#d" \
			    -e "s/^\(Components:.*\) $comp\(.*\)\$/\1\2/" \
				"$release.pre" > "$release.tmp"
			mv -f "$release.tmp" "$release.pre"
		done
		sed -e "s/^\(Components:.*\)\$/\1 $components/" \
			"$release.pre" > "$release"
		rm -f "$release.pre"
	else
		cat > "$release" <<- __EOF__
			Origin: $origin
			Label: $label
			Suite: $suite
			Codename: $codename
			Date: `date -R`
			Architectures: $architectures
			Components: $components
			Description: $description
			MD5Sum:
		__EOF__
	fi
	compare_file
	Verbose 'done'

	Verbose -n 'Appending MD5Sum...'
	for comp in $components; do
		Verbose -n " $comp"
		phash "$pkglist.$comp" "$pkglist_.$comp" "$release"
		phash "$srclist.$comp" "$srclist_.$comp" "$release"
		phash "$pkglist.$comp.bz2" "$pkglist_.$comp.bz2" "$release"
		phash "$srclist.$comp.bz2" "$srclist_.$comp.bz2" "$release"
		phash "$pkglist.$comp.xz" "$pkglist_.$comp.xz" "$release"
		phash "$srclist.$comp.xz" "$srclist_.$comp.xz" "$release"
		phash "$release.$comp" "$release_.$comp" "$release"
	done
	Verbose ' done'

	echo >> "$release"

	if [ -n "$signature" ]; then
		if [ -n "$defaultkey" ]; then
			gpg -armour --quiet --detach-sign --yes --default-key "$defaultkey" "$release"
		else
			gpg -armour --quiet --detach-sign --yes "$release"
		fi

		cat "$release.asc" >>"$release"
		rm -f "$release.asc"
	fi

	# Compare with older release
	compare_file
fi

# Creating old style hashfile
if [ -n "$oldhashfile" -a -z "$listonly" ]; then
	hf="$basedir/hashfile"
	save_file "$hf"
	: > "$hf"

	# TODO: handle 'partial' option

	Verbose -n 'Creating legacy hashfile...'
	echo "MD5SUM:" >> $hf

	for comp in $components; do
		Verbose -n " $comp"

		phash "$pkglist.$comp" "$oldpkglist_.$comp" "$hf"
		phash "$srclist.$comp" "$oldsrclist_.$comp" "$hf"
		phash "$pkglist.$comp.bz2" "$oldpkglist_.$comp.bz2" "$hf"
		phash "$srclist.$comp.bz2" "$oldsrclist_.$comp.bz2" "$hf"
		phash "$pkglist.$comp.xz" "$oldpkglist_.$comp.xz" "$hf"
		phash "$srclist.$comp.xz" "$oldsrclist_.$comp.xz" "$hf"
		phash "$release.$comp" "$oldrelease_.$comp" "$hf"
	done
	Verbose ' done'
	echo >> $hf

	compare_file

	if [ -n "$signature" ]; then
		# Save older hashfile.gpg
		save_file "$basedir/hashfile.gpg"

		if [ -n "$defaultkey" ]; then
			gpg -armour --quiet --sign --yes -o "$basedir/hashfile.gpg" --default-key "$defaultkey" "$basedir/hashfile"
		else
			gpg -armour --quiet --sign --yes -o "$basedir/hashfile.gpg" "$basedir/hashfile"
		fi

		# Compare with older hashfile.gpg
		compare_file
	fi
fi

remove_uncompressed

Verbose 'All your base are belong to us!!!'

# vim:ts=4:sw=4
