#!/bin/bash
# $Id: pkgmgmt,v 1.14 2006/01/22 12:24:25 ensc Exp $

# Copyright (C) 2004,2005 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
#  
# This program 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; version 2 of the License.
#  
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

: ${UTIL_VSERVER_VARS:=/usr/lib/util-vserver/util-vserver-vars}
test -e "$UTIL_VSERVER_VARS" || {
    echo $"Can not find util-vserver installation (the file '$UTIL_VSERVER_VARS' would be expected); aborting..." >&2
    exit 1
}
. "$UTIL_VSERVER_VARS"
. "$_LIB_FUNCTIONS"
. "$_LIB_VSERVER_BUILD_FUNCTIONS_PKGMGMT"
. "$__PKGLIBDIR/vserver.functions"

function showHelp()
{
    echo \
$"Usage: $0 --externalize|--internalize [-y] [--] <vserver-name>

Report bugs to <$PACKAGE_BUGREPORT>."
    exit 0
}

function showVersion()
{
    echo $"\
vpkg $PACKAGE_VERSION -- shows information about packages in vservers
This program is part of $PACKAGE_STRING

Copyright (C) 2004,2005 Enrico Scholz
This program is free software; you may redistribute it under the terms of
the GNU General Public License.  This program has absolutely no warranty."
    exit 0
}

function init()
{
    if test -z "$WORKAROUND_106057"; then
	rpmdb_mntpoint=/dev
    else
	rpmdb_mntpoint=/.rpmdb
    fi
    pkgmgmt.initVariables
}

function _createDirs()
{
    for i; do
	test -n "$i" || continue
	mkdir -p -m755 "$i"
    done
}

function _copySecure()
{
    local chroot=$1
    local srcdir=$2
    local dstdir=$3

    
    ( cd "$srcdir" && tar chf - '.' ) | \
    ( cd "$chroot" && $_EXEC_CD "$dstdir" $_TAR xf - )
}

function _copySecureRev()
{
    local chroot=$1
    local srcdir=$2
    local dstdir=$3

    ( cd "$chroot" && $_EXEC_CD "$srcdir" $_TAR cf - '.' ) | \
    ( cd "$dstdir" && tar xf - )
}

## Usage: _substFile <filename> <sed-expression>
function _substFile()
{
    local file=$1
    local expr=$2
    
    $_CHROOT_SH testfile "$file" || return 0

    local tmp=$($_MKTEMP pkgmgmt_subst.XXXXXX)
    trap "$_RM -f $tmp" EXIT

    $_CHROOT_SH cat "$file" | \
	$_SED -e "$expr" >$tmp

    $_CHROOT_SH cat "$file" | $_CMP -s $tmp - || \
	$_CHROOT_SH truncate "$file" <$tmp

    $_RM -f $tmp
}

function _hashAuto()
{
    local file=$1
    local hash=$2

    $_CHROOT_SH testfile "$file" || return 0
    
    local tmp=$($_MKTEMP apt.conf.XXXXXX)
    trap "$_RM -f $tmp" EXIT

    $_CHROOT_SH cat "$file" | \
	$_SED -e "s|^\([^$hash].*@autogenerated@\)|$hash$hash\1|" >$tmp

    $_CHROOT_SH cat "$file" | $_CMP -s $tmp - || \
	$_CHROOT_SH truncate "$file" <$tmp

    $_RM -f $tmp
}

function _unhashAuto()
{
    test -e "$1" || return 0

    local hash=$2
    local tmp=$($_MKTEMP apt.conf.XXXXXX)
    trap "$_RM -f $tmp" EXIT

    $_SED -e "s|^$hash$hash\(.*@autogenerated@\)|\1|" "$1" >$tmp
    $_CMP -s "$tmp" "$1" || \
	$_CAT "$tmp" >"$1"

    $_RM -f $tmp
}

function _mountFilesystemsInternal()
{
    local fstab="$1"
    test -e "$fstab" || return 0
    shift

    pushd "$vdir" >/dev/null
    "$@" $_SECURE_MOUNT -n -a --chroot --fstab "$fstab"
    popd >/dev/null
}

function _mountFilesystems()
{
    local cfgdir
    cfgdir=$($_VSERVER_INFO "$1" CFGDIR) || {
	echo "Can not determine configuration directory for '$1'; ..." >&2
	return 1
    }
    test -n "$_HAVE_CHBIND_OPTIONS" || _generateChbindOptions "$1"
    
    _mountFilesystemsInternal "$cfgdir"/fstab       $_CHBIND "${CHBIND_OPTS[@]}" || return 1
    _mountFilesystemsInternal "$cfgdir"/fstab.local $_CHBIND "${CHBIND_OPTS[@]}" || return 1
}

function _umountFilesystems()
{
    local cfgdir
    cfgdir=$($_VSERVER_INFO "$1" CFGDIR) || {
	echo "Can not determine configuration directory for '$1'; ..." >&2
	return 1
    }
    local vdir=$cfgdir/vdir
    local is_ok=1
    test -n "$_HAVE_CHBIND_OPTIONS" || _generateChbindOptions "$1"

    pushd "$vdir/" >/dev/null || return 1
	_umountVserverInternal  "$cfgdir"/fstab.local                              || is_ok=
	_umountVserverInternal  "$cfgdir"/fstab       $_CHBIND "${CHBIND_OPTS[@]}" || is_ok=
    popd >/dev/null           || return 1
    
    test -n "$is_ok"
}

# Usage: verifyInternalPackages <vserver> <style>
function verifyInternalPackages()
{
    local pkgs res=0
    local ERR="\
The following errors occured while trying to internalize the
packagemanagement:
"
    
    case $2 in
	(RH)	pkgs=$(vrpm "$1" -- -q --qf '---%{NAME}---\n' rpm apt yum "${YUM_RELEASEPKGS[@]}")
		hasSubstring "$pkgs" ---rpm--- || {
		    warning "$ERR
* The vserver does not seem to have the 'rpm' package which is required
  for internal package management. It is suggested to install it before
  continuing."
		    res=1
		    ERR=
		}
		
		hasSubstring "$pkgs"  ---apt--- ---yum--- || {
		    warning "$ERR
* The vserver does not seem to have a depsolver like 'apt' or 'yum'
  installed. It is suggested to install such a program before setting
  up internal package management."
		    res=1
		    ERR=
		}

		test -n "$have_apt" || test -z "$have_yum"   || \
		hasSubstring "$pkgs" "${YUM_RELEASEPKGS[@]}" || {
		    warning "$ERR
* yum requires a special package which describes the version of the
  distribution. Such a package could not be found within the vserver
  so please install it before continuing. Usually, this package is
  named 'redhat-release' of 'fedora-release'."
		    res=1
		    ERR=
		}
		;;
    esac

    return $res
}

function processVserver_RH()
{
    local vserver=$1
    local is_internalize=$2
    local have_apt
    local cfgdir
    local i

    cfgdir=$($_VSERVER_INFO "$vserver" APPDIR pkgmgmt) || \
    cfgdir=$($_VSERVER_INFO "$vserver" APPDIR)/pkgmgmt

    ## Figure out the environment....
    have_apt=1
    have_yum=1
    pkgmgmt.isAptAvailable "$cfgdir" "$vdir" "$is_internalize" || have_apt=
    pkgmgmt.isYumAvailable "$cfgdir" "$vdir" "$is_internalize" || have_yum=

    local APTETCDIR=
    local APTSTATEDIR=
    local APTCACHEDIR=
    local APTARCHIVDIR=
    local RPMETCDIR=
    local RPMSTATEDIR=

    ## Create directories and assign variables where configuration
    ## can/will be found on the host
    if test -n "$is_internalize"; then
	verifyInternalPackages "$vserver" RH || test -n "$IS_FORCE" ||
	    panic "
Can not continue; use '--force' to override this check"
    
	pushd "$vdir" >/dev/null

	test ! -L var/lib/rpm || {
	    $_EXEC_CD /var/lib $_RM            rpm &&
	    $_EXEC_CD /var/lib $_MKDIR -m755   rpm &&
	    $_EXEC_CD /var/lib $_CHOWN rpm:rpm rpm ||
	    :
	} </dev/null 2>/dev/null

	for i in var/cache/apt/{,archives/{,partial},genpkglist,gensrclist} \
		 var/state/{,apt/{,lists/{,partial}}} \
		 etc/apt etc/rpm; do
	    test -d "$i" ||
		$_EXEC_CD /$(dirname "$i") $_MKDIR -m755 $(basename "$i") || :
	done #2>/dev/null
	
	popd >/dev/null
	
	if test -n "$have_apt"; then
	    findDir APTETCDIR    "$cfgdir"/aptetc "$cfgdir"/base/apt/etc /etc/apt /
	fi

	findDir RPMETCDIR   "$cfgdir"/rpmetc   "$cfgdir"/base/rpm/etc    /etc/rpm /
	findDir RPMSTATEDIR "$cfgdir"/rpmstate "$cfgdir"/base/rpm/state
    else
	mkdir -m755 -p "$cfgdir"
	local need_base=

	if test -n "$have_apt"; then
	    findDir APTETCDIR    "$cfgdir"/aptetc      "$cfgdir"/base/apt/etc       /
	    findDir APTSTATEDIR  "$cfgdir"/aptstate    "$cfgdir"/base/apt/state     /
	    findDir APTCACHEDIR  "$cfgdir"/aptcache    "$cfgdir"/base/apt/cache     /
	    findDir APTARCHIVDIR "$cfgdir"/aptarchives "$cfgdir"/base/apt/archives  /
	    
	    test "$APTETCDIR"    != / || APTETCDIR=$cfgdir/base/apt/etc
	    test "$APTSTATEDIR"  != / || APTSTATEDIR=$cfgdir/base/apt/state
	    test "$APTCACHEDIR"  != / || APTCACHEDIR=$cfgdir/base/apt/cache
	    test "$APTARCHIVDIR" != / || APTARCHIVDIR=$cfgdir/base/apt/archive

	    test -d "$cfgdir"/aptetc   -a -d "$cfgdir"/aptstate -a \
	         -d "$cfgdir"/aptcache -a -d "$cfgdir"/aptarchives || need_base=1
	fi

	findDir RPMETCDIR     "$cfgdir"/rpmetc   "$cfgdir"/base/rpm/etc    /
	findDir RPMSTATEDIR   "$cfgdir"/rpmstate "$cfgdir"/base/rpm/state  /

	test "$RPMETCDIR"   != / || RPMETCDIR=$cfgdir/base/rpm/etc
	test "$RPMSTATEDIR" != / || RPMSTATEDIR=$cfgdir/base/rpm/state

	test -d "$cfgdir"/rpmetc -a -d "$cfgdir"/rpmstate || need_base=1
	test ! -e "$cfgdir"/base || need_base=

	test -z "$need_base" || ln -s "$PKGCFGDIR" "$cfgdir"/base

	mkdir -m755 -p "$PKGCFGDIR"
	_createDirs "$APTETCDIR" "$APTSTATEDIR" "$APTCACHEDIR" "$APTARCHIVDIR" \
		    "$RPMETCDIR" "$RPMSTATEDIR"
    fi

    ## Copy the files...
    if test -n "$is_internalize"; then
	if test -n "$have_apt"; then
	    _copySecure "$vdir" "$APTETCDIR" /etc/apt
	    pushd "$vdir" >/dev/null
		_hashAuto /etc/apt/apt.conf '/'
	    popd >/dev/null
	fi

	_copySecure "$vdir" "$RPMETCDIR"   /etc/rpm
	_copySecure "$vdir" "$RPMSTATEDIR" /var/lib/rpm

	pushd "$vdir" >/dev/null
	    ## remove %_dbpath settings
	    _substFile /etc/rpm/macros '/^%_dbpath[ \t].*/D'
	popd >/dev/null
    else
	if test -n "$have_apt"; then
	    _copySecureRev "$vdir" /etc/apt "$APTETCDIR"
	    _unhashAuto "$APTETCDIR"/apt.conf '/'
	fi

	_copySecureRev "$vdir" /etc/rpm     "$RPMETCDIR"
	_copySecureRev "$vdir" /var/lib/rpm "$RPMSTATEDIR"

	echo -e "%_dbpath\t\t$rpmdb_mntpoint" >>$RPMETCDIR/macros
    fi

    ## Cleanups...
    if test -n "$is_internalize"; then
	:
    else
	tmpdir=$($_MKTEMPDIR -p /var/tmp pkgmgmt.XXXXXX)
	trap "$_RM -rf $tmpdir" EXIT
	pushd "$vdir" >/dev/null
	$_EXEC_CD /var/lib $_MV rpm $tmpdir/
	$_EXEC_CD /var/lib $_LN_S "$rpmdb_mntpoint" rpm
	$_RM -rf $tmpdir
    fi

    ## Finish it...
    if test -n "$is_internalize"; then
	$_TOUCH "$cfgdir"/internal
    else
	$_RM -f "$cfgdir"/internal
    fi
}

function processVserver_Debian()
{
    local vserver=$1
    local is_internalize=$2

    if test -n "$is_internalize"; then
	echo $"Debian vservers should be internalized everytime; do not know how to handle '$vserver'" >&2
    else
	echo $"External packagemanagement is not supported for Debian vserver" >&2
    fi

    return 1
}

function processVserver()
{
    local vserver=$1
    local is_external=
    local skip=1
    local vdir

    ! $_VSERVER_INFO -q "$vserver" RUNNING || {
	echo $"Can not operate on running vservers; please stop '$vserver' and retry again..."
	return 1
    } >&2

    vdir=$($_VSERVER_INFO "$vserver" VDIR) && test -d "$vdir" || {
	echo $"Vserver '$vserver' does not seem to exist; skipping it..."
	return 1
    } >&2
    
    pkgmgmt.isInternal "$vserver" || is_external=1

    case "$is_external"X"$IS_INTERNALIZE"X"$IS_EXTERNALIZE" in
	(*X1X1)	echo $"Can not externalize and internalize at the same time";;
	(*XX)	echo $"No operation specified; try '--help' for more information";;
	(1XX1)	echo $"Vserver '$vserver' has already external packagemanagment; skipping it...";;
	(X1X)	echo $"Vserver '$vserver' has already internal packagemanagment; skipping it...";;
	(*)	skip=
    esac >&2

    test -z "$skip" || return 1

    local style
    _mountFilesystems  "$vserver"       || return 1
    pkgmgmt.guessStyle "$vserver" style || return 1

    case "$style" in
	(redhat|mandrake)	processVserver_RH     "$vserver" "$IS_INTERNALIZE";;
	(debian)		processVserver_Debian "$vserver" "$IS_INTERNALIZE";;
	(*)
	    echo $"Vserver style '$style' is not supported for packagemanagment" >&2
	    return 1
    esac

    _umountFilesystems "$vserver"       || return 1
}

tmp=$(getopt -o y --long debug,externalize,internalize,help,version,force -n "$0" -- "$@") || exit 1
eval set -- "$tmp"

IS_EXTERNALIZE=
IS_INTERNALIZE=
IS_YES=
IS_FORCE=

while true; do
    case "$1" in
	(--help)	showHelp $0;;
	(--version)	showVersion;;
	(--debug)	set -x;;
	(--externalize)	IS_EXTERNALIZE=1;;
	(--internalize)	IS_INTERNALIZE=1;;
	(--force)       IS_FORCE=1;;
	(-y)		IS_YES=1;;
	(--)		shift; break;;
	(*)	   	echo $"vserver: internal error; arg=='$1'" >&2; exit 1;;
    esac
    shift
done

test -n "$1" || {
    echo $"No vserver specified; try '--help' for more information"
    exit 1
} >&2


set -e
init

ok=1
passed=
for i; do
    processVserver "$i" && passed=1 || ok=
done
    
test -z "$ok"     || exit 0
test -z "$passed" || exit 1
exit 2
