added '--keep' option
[util-vserver.git] / util-vserver / scripts / pkgmgmt
1 #!/bin/bash
2 # $Id$
3
4 # Copyright (C) 2004,2005 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
5 #  
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; version 2 of the License.
9 #  
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #  
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19 : ${UTIL_VSERVER_VARS:=/usr/lib/util-vserver/util-vserver-vars}
20 test -e "$UTIL_VSERVER_VARS" || {
21     echo $"Can not find util-vserver installation (the file '$UTIL_VSERVER_VARS' would be expected); aborting..." >&2
22     exit 1
23 }
24 . "$UTIL_VSERVER_VARS"
25 . "$_LIB_FUNCTIONS"
26 . "$_LIB_VSERVER_BUILD_FUNCTIONS_PKGMGMT"
27 . "$__PKGLIBDIR/vserver.functions"
28
29 function showHelp()
30 {
31     echo \
32 $"Usage: $0 --externalize|--internalize [-y] [--] <vserver-name>
33
34 Report bugs to <$PACKAGE_BUGREPORT>."
35     exit 0
36 }
37
38 function showVersion()
39 {
40     echo $"\
41 vpkg $PACKAGE_VERSION -- shows information about packages in vservers
42 This program is part of $PACKAGE_STRING
43
44 Copyright (C) 2004,2005 Enrico Scholz
45 This program is free software; you may redistribute it under the terms of
46 the GNU General Public License.  This program has absolutely no warranty."
47     exit 0
48 }
49
50 function init()
51 {
52     if test -z "$WORKAROUND_106057"; then
53         rpmdb_mntpoint=/dev
54     else
55         rpmdb_mntpoint=/.rpmdb
56     fi
57     pkgmgmt.initVariables
58 }
59
60 function _createDirs()
61 {
62     for i; do
63         test -n "$i" || continue
64         mkdir -p -m755 "$i"
65     done
66 }
67
68 function _copySecure()
69 {
70     local chroot=$1
71     local srcdir=$2
72     local dstdir=$3
73
74     
75     ( cd "$srcdir" && tar chf - '.' ) | \
76     ( cd "$chroot" && $_EXEC_CD "$dstdir" $_TAR xf - )
77 }
78
79 function _copySecureRev()
80 {
81     local chroot=$1
82     local srcdir=$2
83     local dstdir=$3
84
85     ( cd "$chroot" && $_EXEC_CD "$srcdir" $_TAR cf - '.' ) | \
86     ( cd "$dstdir" && tar xf - )
87 }
88
89 function _hashAuto()
90 {
91     local hash=$2
92     local dir=$(dirname "$1")
93     local file=$(basename "$1")
94
95     $_EXEC_CD "$dir" "$(which test)" "$file" || return 0
96     
97     local tmp=$($_MKTEMP /tmp/apt.conf.XXXXXX)
98     trap "$_RM -f $tmp" EXIT
99
100     $_EXEC_CD "$dir" $_CAT "$file" | \
101         $_SED -e "s|^\([^$hash].*@autogenerated@\)|$hash$hash\1|" >$tmp
102
103     $_CAT $tmp | $_EXEC_CD "$dir" "$(which cmp)" -s - "$file" || \
104         $_CHROOT_SH truncate "$1" <$tmp
105
106     $_RM -f $tmp
107 }
108
109 function _unhashAuto()
110 {
111     test -e "$1" || return 0
112
113     local hash=$2
114     local tmp=$($_MKTEMP /tmp/apt.conf.XXXXXX)
115     trap "$_RM -f $tmp" EXIT
116
117     $_SED -e "s|^$hash$hash\(.*@autogenerated@\)|\1|" "$1" >$tmp
118     $_CMP -s "$tmp" "$1" || \
119         $_CAT "$tmp" >"$1"
120
121     $_RM -f $tmp
122 }
123
124 function _mountFilesystemsInternal()
125 {
126     local fstab="$1"
127     test -e "$fstab" || return 0
128     shift
129
130     pushd "$vdir" >/dev/null
131     "$@" $_SECURE_MOUNT -n -a --chroot --fstab "$fstab"
132     popd >/dev/null
133 }
134
135 function _mountFilesystems()
136 {
137     local cfgdir
138     cfgdir=$($_VSERVER_INFO "$1" CFGDIR) || {
139         echo "Can not determine configuration directory for '$1'; ..." >&2
140         return 1
141     }
142     test -n "$_HAVE_CHBIND_OPTIONS" || _generateChbindOptions "$1"
143     
144     _mountFilesystemsInternal "$cfgdir"/fstab       $_CHBIND "${CHBIND_OPTS[@]}" || return 1
145     _mountFilesystemsInternal "$cfgdir"/fstab.local $_CHBIND "${CHBIND_OPTS[@]}" || return 1
146 }
147
148 function _umountFilesystems()
149 {
150     local cfgdir
151     cfgdir=$($_VSERVER_INFO "$1" CFGDIR) || {
152         echo "Can not determine configuration directory for '$1'; ..." >&2
153         return 1
154     }
155     local vdir=$cfgdir/vdir
156     local is_ok=1
157     test -n "$_HAVE_CHBIND_OPTIONS" || _generateChbindOptions "$1"
158
159     pushd "$vdir/" >/dev/null || return 1
160         _umountVserverInternal  "$cfgdir"/fstab.local                              || is_ok=
161         _umountVserverInternal  "$cfgdir"/fstab       $_CHBIND "${CHBIND_OPTS[@]}" || is_ok=
162     popd >/dev/null           || return 1
163     
164     test -n "$is_ok"
165 }
166
167 # Usage: verifyInternalPackages <vserver> <style>
168 function verifyInternalPackages()
169 {
170     local pkgs res=0
171     local ERR="\
172 The following errors occured while trying to internalize the
173 packagemanagement:
174 "
175     
176     case $2 in
177         (RH)    pkgs=$(vrpm "$1" -- -q --qf '---%{NAME}---\n' rpm apt yum "${YUM_RELEASEPKGS[@]}")
178                 hasSubstring "$pkgs" ---rpm--- || {
179                     warning "$ERR
180 * The vserver does not seem to have the 'rpm' package which is required
181   for internal package management. It is suggested to install it before
182   continuing."
183                     res=1
184                     ERR=
185                 }
186                 
187                 hasSubstring "$pkgs"  ---apt--- ---yum--- || {
188                     warning "$ERR
189 * The vserver does not seem to have a depsolver like 'apt' or 'yum'
190   installed. It is suggested to install such a program before setting
191   up internal package management."
192                     res=1
193                     ERR=
194                 }
195
196                 test -n "$have_apt" || test -z "$have_yum"   || \
197                 hasSubstring "$pkgs" "${YUM_RELEASEPKGS[@]}" || {
198                     warning "$ERR
199 * yum requires a special package which describes the version of the
200   distribution. Such a package could not be found within the vserver
201   so please install it before continuing. Usually, this package is
202   named 'redhat-release' of 'fedora-release'."
203                     res=1
204                     ERR=
205                 }
206                 ;;
207     esac
208
209     return $res
210 }
211
212 function processVserver_RH()
213 {
214     local vserver=$1
215     local is_internalize=$2
216     local have_apt
217     local cfgdir
218     local i
219
220     cfgdir=$($_VSERVER_INFO "$vserver" APPDIR pkgmgmt) || \
221     cfgdir=$($_VSERVER_INFO "$vserver" APPDIR)/pkgmgmt
222
223     ## Figure out the environment....
224     have_apt=1
225     have_yum=1
226     pkgmgmt.isAptAvailable "$cfgdir" "$vdir" "$is_internalize" || have_apt=
227     pkgmgmt.isYumAvailable "$cfgdir" "$vdir" "$is_internalize" || have_yum=
228
229     local APTETCDIR=
230     local APTSTATEDIR=
231     local APTCACHEDIR=
232     local APTARCHIVDIR=
233     local RPMETCDIR=
234     local RPMSTATEDIR=
235
236     ## Create directories and assign variables where configuration
237     ## can/will be found on the host
238     if test -n "$is_internalize"; then
239         verifyInternalPackages "$vserver" RH || test -n "$IS_FORCE" ||
240             panic "
241 Can not continue; use '--force' to override this check"
242     
243         pushd "$vdir" >/dev/null
244
245         test ! -L var/lib/rpm || {
246             $_EXEC_CD /var/lib $_RM            rpm &&
247             $_EXEC_CD /var/lib $_MKDIR -m755   rpm &&
248             $_EXEC_CD /var/lib $_CHOWN rpm:rpm rpm ||
249             :
250         } </dev/null 2>/dev/null
251
252         for i in var/cache/apt/{,archives/{,partial},genpkglist,gensrclist} \
253                  var/state/{,apt/{,lists/{,partial}}} \
254                  etc/apt etc/rpm; do
255             test -d "$i" ||
256                 $_EXEC_CD /$(dirname "$i") $_MKDIR -m755 $(basename "$i") || :
257         done #2>/dev/null
258         
259         popd >/dev/null
260         
261         if test -n "$have_apt"; then
262             findDir APTETCDIR    "$cfgdir"/aptetc "$cfgdir"/base/apt/etc /etc/apt /
263         fi
264
265         findDir RPMETCDIR   "$cfgdir"/rpmetc   "$cfgdir"/base/rpm/etc    /etc/rpm /
266         findDir RPMSTATEDIR "$cfgdir"/rpmstate "$cfgdir"/base/rpm/state
267     else
268         mkdir -m755 -p "$cfgdir"
269         local need_base=
270
271         if test -n "$have_apt"; then
272             findDir APTETCDIR    "$cfgdir"/aptetc      "$cfgdir"/base/apt/etc       /
273             findDir APTSTATEDIR  "$cfgdir"/aptstate    "$cfgdir"/base/apt/state     /
274             findDir APTCACHEDIR  "$cfgdir"/aptcache    "$cfgdir"/base/apt/cache     /
275             findDir APTARCHIVDIR "$cfgdir"/aptarchives "$cfgdir"/base/apt/archives  /
276             
277             test "$APTETCDIR"    != / || APTETCDIR=$cfgdir/base/apt/etc
278             test "$APTSTATEDIR"  != / || APTSTATEDIR=$cfgdir/base/apt/state
279             test "$APTCACHEDIR"  != / || APTCACHEDIR=$cfgdir/base/apt/cache
280             test "$APTARCHIVDIR" != / || APTARCHIVDIR=$cfgdir/base/apt/archive
281
282             test -d "$cfgdir"/aptetc   -a -d "$cfgdir"/aptstate -a \
283                  -d "$cfgdir"/aptcache -a -d "$cfgdir"/aptarchives || need_base=1
284         fi
285
286         findDir RPMETCDIR     "$cfgdir"/rpmetc   "$cfgdir"/base/rpm/etc    /
287         findDir RPMSTATEDIR   "$cfgdir"/rpmstate "$cfgdir"/base/rpm/state  /
288
289         test "$RPMETCDIR"   != / || RPMETCDIR=$cfgdir/base/rpm/etc
290         test "$RPMSTATEDIR" != / || RPMSTATEDIR=$cfgdir/base/rpm/state
291
292         test -d "$cfgdir"/rpmetc -a -d "$cfgdir"/rpmstate || need_base=1
293         test ! -e "$cfgdir"/base || need_base=
294
295         test -z "$need_base" || ln -s "$PKGCFGDIR" "$cfgdir"/base
296
297         mkdir -m755 -p "$PKGCFGDIR"
298         _createDirs "$APTETCDIR" "$APTSTATEDIR" "$APTCACHEDIR" "$APTARCHIVDIR" \
299                     "$RPMETCDIR" "$RPMSTATEDIR"
300     fi
301
302     ## Copy the files...
303     if test -n "$is_internalize"; then
304         if test -n "$have_apt"; then
305             _copySecure "$vdir" "$APTETCDIR" /etc/apt
306             pushd "$vdir" >/dev/null
307                 _hashAuto /etc/apt/apt.conf '/'
308             popd >/dev/null
309         fi
310
311         _copySecure "$vdir" "$RPMETCDIR"   /etc/rpm
312         _copySecure "$vdir" "$RPMSTATEDIR" /var/lib/rpm
313
314     else
315         if test -n "$have_apt"; then
316             _copySecureRev "$vdir" /etc/apt "$APTETCDIR"
317             _unhashAuto "$APTETCDIR"/apt.conf '/'
318         fi
319
320         _copySecureRev "$vdir" /etc/rpm     "$RPMETCDIR"
321         _copySecureRev "$vdir" /var/lib/rpm "$RPMSTATEDIR"
322     fi
323
324     ## Cleanups...
325     if test -n "$is_internalize"; then
326         :
327     else
328         tmpdir=$($_MKTEMPDIR /var/tmp/pgmgmt.XXXXXX)
329         trap "$_RM -rf $tmpdir" EXIT
330         pushd "$vdir" >/dev/null
331         $_EXEC_CD /var/lib $_MV rpm $tmpdir/
332         $_EXEC_CD /var/lib $_LN_S "$rpmdb_mntpoint" rpm
333         $_RM -rf $tmpdir
334     fi
335
336     ## Finish it...
337     if test -n "$is_internalize"; then
338         $_TOUCH "$cfgdir"/internal
339     else
340         $_RM -f "$cfgdir"/internal
341     fi
342 }
343
344 function processVserver_Debian()
345 {
346     local vserver=$1
347     local is_internalize=$2
348
349     if test -n "$is_internalize"; then
350         echo $"Debian vservers should be internalized everytime; do not know how to handle '$vserver'" >&2
351     else
352         echo $"External packagemanagement is not supported for Debian vserver" >&2
353     fi
354
355     return 1
356 }
357
358 function processVserver()
359 {
360     local vserver=$1
361     local is_external=
362     local skip=1
363     local vdir
364
365     ! $_VSERVER_INFO -q "$vserver" RUNNING || {
366         echo $"Can not operate on running vservers; please stop '$vserver' and retry again..."
367         return 1
368     } >&2
369
370     vdir=$($_VSERVER_INFO "$vserver" VDIR) && test -d "$vdir" || {
371         echo $"Vserver '$vserver' does not seem to exist; skipping it..."
372         return 1
373     } >&2
374     
375     pkgmgmt.isInternal "$vserver" || is_external=1
376
377     case "$is_external"X"$IS_INTERNALIZE"X"$IS_EXTERNALIZE" in
378         (*X1X1) echo $"Can not externalize and internalize at the same time";;
379         (*XX)   echo $"No operation specified; try '--help' for more information";;
380         (1XX1)  echo $"Vserver '$vserver' has already external packagemanagment; skipping it...";;
381         (X1X)   echo $"Vserver '$vserver' has already internal packagemanagment; skipping it...";;
382         (*)     skip=
383     esac >&2
384
385     test -z "$skip" || return 1
386
387     local style
388     _mountFilesystems  "$vserver"       || return 1
389     pkgmgmt.guessStyle "$vserver" style || return 1
390
391     case "$style" in
392         (redhat|mandrake)       processVserver_RH     "$vserver" "$IS_INTERNALIZE";;
393         (debian)                processVserver_Debian "$vserver" "$IS_INTERNALIZE";;
394         (*)
395             echo $"Vserver style '$style' is not supported for packagemanagment" >&2
396             return 1
397     esac
398
399     _umountFilesystems "$vserver"       || return 1
400 }
401
402 tmp=$(getopt -o y --long debug,externalize,internalize,help,version,force -n "$0" -- "$@") || exit 1
403 eval set -- "$tmp"
404
405 IS_EXTERNALIZE=
406 IS_INTERNALIZE=
407 IS_YES=
408 IS_FORCE=
409
410 while true; do
411     case "$1" in
412         (--help)        showHelp $0;;
413         (--version)     showVersion;;
414         (--debug)       set -x;;
415         (--externalize) IS_EXTERNALIZE=1;;
416         (--internalize) IS_INTERNALIZE=1;;
417         (--force)       IS_FORCE=1;;
418         (-y)            IS_YES=1;;
419         (--)            shift; break;;
420         (*)             echo $"vserver: internal error; arg=='$1'" >&2; exit 1;;
421     esac
422     shift
423 done
424
425 test -n "$1" || {
426     echo $"No vserver specified; try '--help' for more information"
427     exit 1
428 } >&2
429
430
431 set -e
432 init
433
434 ok=1
435 passed=
436 for i; do
437     processVserver "$i" && passed=1 || ok=
438 done
439     
440 test -z "$ok"     || exit 0
441 test -z "$passed" || exit 1
442 exit 2