diff options
author | Rene Ladan <rene@FreeBSD.org> | 2014-08-26 18:57:57 +0000 |
---|---|---|
committer | Rene Ladan <rene@FreeBSD.org> | 2014-08-26 18:57:57 +0000 |
commit | 40771ce9faa58fd1584057ca4eb185f906767379 (patch) | |
tree | 2474d9034824451d82e3d3854de3187bfbc86807 /sysutils | |
parent | 0fdef3fcfebb6beb2f30e454ebc9d6b06fe32c23 (diff) | |
download | freebsd-ports-40771ce9faa58fd1584057ca4eb185f906767379.zip |
sysutils/bsdadminscripts: fix scripts at runtime.
Previously, pkg_libchk, pkg_upgrade, and uma failed to run.
Bump PORTREVISION
PR: 193003
Submitted by: Carlos Jacobo Puga Medina <cpm@fbsd.es>
Reviewed by: marino
Diffstat (limited to 'sysutils')
-rw-r--r-- | sysutils/bsdadminscripts/Makefile | 12 | ||||
-rw-r--r-- | sysutils/bsdadminscripts/files/patch-pkg_libchk | 75 | ||||
-rw-r--r-- | sysutils/bsdadminscripts/files/pkg_libchk.in | 484 | ||||
-rw-r--r-- | sysutils/bsdadminscripts/files/pkg_upgrade.in | 2239 | ||||
-rw-r--r-- | sysutils/bsdadminscripts/files/uma.in | 436 |
5 files changed, 3170 insertions, 76 deletions
diff --git a/sysutils/bsdadminscripts/Makefile b/sysutils/bsdadminscripts/Makefile index d4fbb7ef8a25..87936eceb3af 100644 --- a/sysutils/bsdadminscripts/Makefile +++ b/sysutils/bsdadminscripts/Makefile @@ -3,7 +3,7 @@ PORTNAME= bsdadminscripts PORTVERSION= 6.1.1 -PORTREVISION= 6 +PORTREVISION= 7 CATEGORIES= sysutils ports-mgmt MASTER_SITES= SF/${PORTNAME}/${PORTNAME} @@ -13,10 +13,17 @@ COMMENT= Collection of administration scripts LICENSE= BSD2CLAUSE NO_BUILD= yes + +TMP?= /tmp +VAR?= /var + PORTDOCS= ABOUT CHANGES INSTALL NOTES THANKS OPTIONS_DEFINE= DOCS +SUB_FILES= pkg_libchk pkg_upgrade uma +SUB_LIST= TMP=${TMP} PREFIX=${PREFIX} VAR=${VAR} PORTS=${PORTSDIR} + .include <bsd.port.options.mk> .if ! ${PORT_OPTIONS:MDOCS} @@ -31,12 +38,14 @@ do-install: -datadir=${STAGEDIR}${DATADIR} \ ${EVALDOCS} .for n in pkg_libchk pkg_upgrade uma + ${MV} ${WRKDIR}/${n} ${WRKSRC}/src ${INSTALL_SCRIPT} ${WRKSRC}/src/${n} ${STAGEDIR}${PREFIX}/sbin .endfor ${INSTALL_DATA} ${WRKSRC}/src/buildflags.mk ${STAGEDIR}${DATADIR} ${INSTALL_DATA} ${WRKSRC}/src/buildflags.conf.sample \ ${STAGEDIR}${PREFIX}/etc ${INSTALL_DATA} ${WRKSRC}/src/uma.conf.sample ${STAGEDIR}${PREFIX}/etc + .for f in bsdadminscripts buildflags.awk buildflags.conf buildflags.mk \ distviper pkg_libchk pkg_upgrade pkg_validate portconfig rcstart uma ${INSTALL_MAN} ${WRKSRC}/src/${f}.1 ${STAGEDIR}${MAN1PREFIX}/man/man1 @@ -46,6 +55,7 @@ post-install: ${MKDIR} ${STAGEDIR}${ETCDIR} ${MV} ${STAGEDIR}${PREFIX}/etc/*.sample ${STAGEDIR}${ETCDIR} ${RM} -rf ${STAGEDIR}${PREFIX}/etc/*.sample + .if ${PORT_OPTIONS:MDOCS} ${MKDIR} ${STAGEDIR}${DOCSDIR} cd ${WRKSRC} && ${INSTALL_DATA} ${PORTDOCS} ${STAGEDIR}${DOCSDIR} diff --git a/sysutils/bsdadminscripts/files/patch-pkg_libchk b/sysutils/bsdadminscripts/files/patch-pkg_libchk deleted file mode 100644 index 5aec34e6800c..000000000000 --- a/sysutils/bsdadminscripts/files/patch-pkg_libchk +++ /dev/null @@ -1,75 +0,0 @@ ---- src/pkg_libchk.orig 2009-04-19 17:57:16.000000000 +0200 -+++ src/pkg_libchk 2013-06-26 22:01:57.000000000 +0200 -@@ -23,6 +23,8 @@ - - readonly name=pkg_libchk - readonly version=1.6.1 -+readonly osname=`uname -s` -+readonly pkgng=`make -f /usr/share/mk/bsd.port.mk -V WITH_PKGNG` - - # Use a line break as delimiter. - IFS=' -@@ -206,7 +208,7 @@ dependencyMissing() { - # We cannot handle non-native binaries, - # so assume everything is in order. - if ! readelf -e "$1" 2>&1 | \ -- grep -E "^[[:space:]]*OS/ABI:[[:space:]]*UNIX - $OSTYPE\$" \ -+ grep -E "^[[:space:]]*OS/ABI:[[:space:]]*UNIX - $osname\$" \ - > /dev/null - then - return 2 -@@ -405,10 +407,17 @@ statusSet 'Preparing ...' - - # Get the packages to work on. - test -z "$packages" && packages="-a" --packages="$(pkg_info -E $packages)" --test -z "$recursive" -a -z "$Recursive" || packages="$packages --$(pkg_info -q $recursive $Recursive "$packages" 2> /dev/null | \ --sed -E 's|^@pkgdep[[:space:]]*||1')" -+if [ -n "$pkgng" ]; then -+ packages="$(pkg info -q $packages)" -+ test -z "$recursive" -a -z "$Recursive" || packages="$packages -+ $(pkg info -q $recursive $Recursive "$packages" 2> /dev/null | \ -+ sed -E 's|^@pkgdep[[:space:]]*||1')" -+else -+ packages="$(pkg_info -E $packages)" -+ test -z "$recursive" -a -z "$Recursive" || packages="$packages -+ $(pkg_info -q $recursive $Recursive "$packages" 2> /dev/null | \ -+ sed -E 's|^@pkgdep[[:space:]]*||1')" -+fi - - # Create the regexp to match ldd output - match_expr="$compat=> not found|dependency .+ not found" -@@ -420,9 +429,15 @@ package_num=0 - # Check each selected package. - for package in $packages; { - package_num="$(($package_num + 1))" -- test $origin \ -- && package_name="$(pkg_info -qo "$package")" \ -- || package_name="$package" -+ if [ -n "$pkgng" ]; then -+ test $origin \ -+ && package_name="$(pkg info -qo "$package")" \ -+ || package_name="$package" -+ else -+ test $origin \ -+ && package_name="$(pkg_info -qo "$package")" \ -+ || package_name="$package" -+ fi - - # Print what we're doing. - statusSet "Starting job $package_num of $package_amount: $package_name" -@@ -432,7 +447,12 @@ for package in $packages; { - # Remember freeing the semaphore. - trap 'semaphoreFree jobs' EXIT - -- files="$(pkg_info -qL "$package")" -+ files="" -+ if [ -n "$pkgng" ]; then -+ files="$(pkg info -lq "$package")" -+ else -+ files="$(pkg_info -qL "$package")" -+ fi - # Get the programs libraries in case it doesn't use the - # operating system to find its libraries. - libraries="$(echo "$files" | grep -E '\.so[\.0-9]*$')" diff --git a/sysutils/bsdadminscripts/files/pkg_libchk.in b/sysutils/bsdadminscripts/files/pkg_libchk.in new file mode 100644 index 000000000000..232e00947f54 --- /dev/null +++ b/sysutils/bsdadminscripts/files/pkg_libchk.in @@ -0,0 +1,484 @@ +#!/bin/sh -f +# +# Copyright (c) 2007-2009 +# Dominic Fandrey <kamikaze@bsdforen.de> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +readonly name=pkg_libchk +readonly version=1.6.1 +readonly osname=`uname -s` +readonly pkgng=`make -f /usr/share/mk/bsd.port.mk -V WITH_PKGNG` + +# Use a line break as delimiter. +IFS=' +' + +# Filename prefix for shared data +sharedprefix="%%TMP%%/$$" +shared="locks" + +# +# This function remembers a lock to allow later deletion with the +# lockUnregisterAll() function. +# +# @param $1 +# The name of the lock. +lockRegister() { + local lock + lock="$sharedprefix-$shared" + lockf -k "$lock" sh -c " + if ! grep -qE '^$1\$' '$lock'; then + echo '$1' >> '$lock' + fi + " +} + +# +# Unregisters all locks. +# +lockUnregisterAll() { + wait + for register in $(cat "$sharedprefix-$shared"); { + lockf "$sharedprefix-$register" wait + } + lockf "$sharedprefix-$shared" wait +} + +# +# This function creates a semaphore. +# +# @param $1 +# The name of the semaphore. +# @param $2 +# The size of the semaphore. +# +semaphoreCreate() { + local lock + lockRegister "semaphore-$1" + lock="$sharedprefix-semaphore-$1" + lockf -k "$lock" echo "$2" > "$lock" + eval "semaphore_$1_size=$2" +} + +# +# This function waits until the semaphore is free und registers its use. +# Everything that uses this also has to call the semaphoreFree() function. +# +# @param $1 +# The name of the semaphore. +# +semaphoreUse() { + local lock semaphores + lock="$sharedprefix-semaphore-$1" + while ! lockf -k "$lock" sh -c " + state=\$(cat '$lock') + if [ \"\$state\" -gt 0 ]; then + echo \"\$((\$state - 1))\" > '$lock' + exit 0 + fi + exit 1 + "; do + sleep 0.1 + done +} + +# +# This function frees a semaphore. +# +# @param $1 +# The name of the semaphore. +# +semaphoreFree() { + local lock + lock="$sharedprefix-semaphore-$1" + lockf -k "$lock" sh -c " + state=\"\$((\"\$(cat '$lock')\" + 1))\" + echo \"\$state\" > '$lock' + " +} + +# +# This function sets a new status and prints it. +# +# @param $1 +# The status message. +# @param $clean +# If set status handling is disabled. +# +statusSet() { + # In clean mode status handling is disabled. + test -z "$clean" || return 0 + local lock + lock="$sharedprefix-status" + lockf -k "$lock" sh -c " + status=\"\$(cat '$lock')\" + echo '$1' > '$lock' + printf \"\\r%-\${#status}s\\r\" '$1' > /dev/tty + " +} + +# +# This function prints a message and the current status behind it. +# +# @param $1 +# The message to print. +# @param $clean +# If set the status will not be printed. +# +statusPrint() { + if [ -z "$clean" ]; then + local lock + lock="$sharedprefix-status" + lockf -k "$lock" sh -c " + status=\"\$(cat '$lock')\" + printf \"%-\${#status}s\\r\" '' > /dev/tty + echo '$1' + printf '%s\\r' \"\$status\" > /dev/tty + " + else + echo "$1" + fi +} + +# +# Waits for a semaphore to be completely free and counts down the remaining +# number of locks. +# +# @param $1 +# The semaphore to watch. +# @param $2 +# The status message to print, insert %d in the place where the number +# of remaining locks belong. +# +semaphoreCountDown() { + local free size + while read -t1 free < "$sharedprefix-semaphore-$1"; do + size=$(eval "echo \$semaphore_$1_size") + statusSet "$(printf "$2" $(( $size - $free )))" + test "$free" -eq "$size" && break + sleep 0.1 + done + wait +} + +# Clean up upon exit. +trap ' + semaphoreCountDown jobs "Terminated by signal, waiting for %d jobs to die." + echo > /dev/tty + lockUnregisterAll + exit 255 +' int term + +# +# This function checks whether a given binary or library directly depends +# on a missing library. +# It goes a long way to prevent all kinds of false positives. +# It always returns 2 (false) for Linux and other non-native libraries +# and binaries. +# It also checks whether the missing dependency is really a direct dependency +# (indirect dependencies have to be fixed somewhere else). +# +# @param $1 +# The library or binary to check. +# @return +# Returns 0 (true) if a library is missing. +# Returns 1 if everything is all right. +# Returns 2 if the check cannot be performed (not a native library). +# +dependencyMissing() { + local missing file direct libfound + + # We cannot handle non-native binaries, + # so assume everything is in order. + if ! readelf -e "$1" 2>&1 | \ + grep -E "^[[:space:]]*OS/ABI:[[:space:]]*UNIX - $osname\$" \ + > /dev/null + then + return 2 + # Nothing is missing. + elif ! missing="$(ldd "$1" 2>&1 | grep -E "$match_expr")"; then + return 1 + fi + + # The return status. The value 1 assumes that this is a false positive. + status=1 + + # Only report misses for direct dependencies. + direct="$( + readelf -d "$1" 2> /dev/null | \ + grep 'Shared library:' | \ + sed -E -e 's|^[^[]*\[||1' -e 's|\]$||1' + )" + + # Compare every missing depency with the list of direct dependencies + # and report that the dependency is missing if the missing file is + # a direct dependency. + for file in $missing; { + # Strip the missing file of additional information. + file="$(echo "$file" | sed -E \ + -e 's| => .*$||1' \ + -e 's|^[[:space:]]*||1' \ + -e 's|^.*dependency ||1' \ + -e 's| not found$||1' + )" + + # If in mean mode we do not check for false positives. + if [ -n "$mean" ]; then + test -n "$raw" && return 0 + statusPrint "$package_name: $1 misses $file" + continue + fi + + # Handle the case where a library is not found, but exists + # somewhere in the package. This is for packages that do not + # rely on the OS to find libraries. + libfound= + for library in $(echo "$libraries" | grep -E "/$file\$"); { + # The library exists after all. + test -e "$library" && libfound=1 && break + } + if test "$libfound"; then + test -n "$verbose" && statusPrint "$package_name: \ +located: $1 misses $file found at $library." + continue + fi + + # Compare the file with the list of direct dependencies. + # If it's not in than it's only an indirect dependency and + # cannot be fixed by rebuilding this port. + if echo "$direct" | grep -E "^$file\$" > /dev/null; then + test -n "$raw" && return 0 + statusPrint "$package_name: $1 misses $file" + status=0 + elif [ -n "$verbose" ]; then + statusPrint "$package_name: inderect: $1 \ +misses $file is an inderect dependency." + fi + } + + return $status +} + +# +# Checks the parameters for options. +# +# @param $packages +# The parameters to pkg_info -E that will result in the +# names of the packages to work on. +# @param $recursive +# Contains the appropriate parameter to get the +# dependencies of the given packages from pkg_info. +# @param $Recursive +# Contains the appropriate parameter to get the +# packages depending on the given packages from pkg_info. +# @param $raw +# Is set to trigger raw printing. +# @param $clean +# Is set to trigger printing without status messages. +# @param $verbose +# Is set to be verbose about false positives. +# @param $mean +# Is set to switch into mean mode. That means no +# checking of false positives. +# @param $compat +# Delete to avoid detecting compat libraries as misses. +# @param $origin +# Is set to turn the print origin mode on. +# @semaphore jobs +# Is set to limit the amount of parallel jobs. +# +readParams() { + local option + + for option { + case "$option" in + "-a" | "--all") + packages="-a" + ;; + "-c" | "--clean") + clean=1 + ;; + "-h" | "--help") + printHelp + ;; + -j* | --jobs*) + local jobs + jobs="${option#-j}" + jobs="${jobs#--jobs}" + if [ "$jobs" -ne "$jobs" ] 2> /dev/null; then + echo "The -j option must be followed" \ + "by a number." + exit 3 + elif [ "$jobs" -lt 1 ]; then + echo "The -j option must specify at" \ + "least 1 job." + exit 3 + else + semaphoreCreate jobs "$jobs" + fi + ;; + "-m" | "--mean") + mean=1 + ;; + "-n" | "--no-compat") + compat= + ;; + "-o" | "--origin") + origin=1 + ;; + "-q" | "--raw") + raw=1 + if [ -n "$verbose" ]; then + echo "The parameters -v and -q may" \ + "not be used at the same time." + exit 2 + fi + ;; + "-r" | "--recursive") + recursive="-r" + ;; + "-R" | "--upward-recursive") + Recursive="-R" + ;; + "-v" | "--verbose") + verbose=1 + if [ -n "$raw" ]; then + echo "The parameters -q and -v may" \ + "not be used at the same time." + exit 2 + fi + ;; + -? | --*) + echo "Unknown parameter \"$option\"." + exit 1 + ;; + -*) + readParams "${option%${option#-?}}" + readParams "-${option#-?}" + ;; + *) + packages="$packages${packages:+$IFS}$option" + ;; + esac + } +} + +# +# Display a short help message. +# +printHelp() { + echo "$name v$version +usage: $name [-a] [-c] [-h] [-jN] [-m] [-n] [-o] [-q] [-r] [-R] [-v] [packages]" + exit 0 +} + +# Create the expression to match to find files linking against compat libraries. +# This can be emptied by readParams to deactivate that feature. +prefix="$(make -f /usr/share/mk/bsd.port.mk -VPREFIX 2> /dev/null || \ + echo '%%PREFIX%%')" +compat="=> $prefix/lib/compat|" + +# Create the semaphore with CPU cores * 2 jobs. +semaphoreCreate jobs "$(($(sysctl -n hw.ncpu 2> /dev/null || echo 1) * 2))" +# Register the status lock. +lockRegister status + +# Read the parameters. +readParams "$@" + +statusSet 'Preparing ...' + +# Get the packages to work on. +test -z "$packages" && packages="-a" +if [ -n "$pkgng" ]; then + packages="$(pkg info -q $packages)" + test -z "$recursive" -a -z "$Recursive" || packages="$packages + $(pkg info -q $recursive $Recursive "$packages" 2> /dev/null | \ + sed -E 's|^@pkgdep[[:space:]]*||1')" +else + packages="$(pkg_info -E $packages)" + test -z "$recursive" -a -z "$Recursive" || packages="$packages + $(pkg_info -q $recursive $Recursive "$packages" 2> /dev/null | \ + sed -E 's|^@pkgdep[[:space:]]*||1')" +fi + +# Create the regexp to match ldd output +match_expr="$compat=> not found|dependency .+ not found" + +# The packages to check. +package_amount="$(echo "$packages" | wc -l | sed 's|[[:space:]]||g')" +package_num=0 + +# Check each selected package. +for package in $packages; { + package_num="$(($package_num + 1))" + if [ -n "$pkgng" ]; then + test $origin \ + && package_name="$(pkg info -qo "$package")" \ + || package_name="$package" + else + test $origin \ + && package_name="$(pkg_info -qo "$package")" \ + || package_name="$package" + fi + + # Print what we're doing. + statusSet "Starting job $package_num of $package_amount: $package_name" + + semaphoreUse jobs + ( + # Remember freeing the semaphore. + trap 'semaphoreFree jobs' EXIT + + files="" + if [ -n "$pkgng" ]; then + files="$(pkg info -lq "$package")" + else + files="$(pkg_info -qL "$package")" + fi + # Get the programs libraries in case it doesn't use the + # operating system to find its libraries. + libraries="$(echo "$files" | grep -E '\.so[\.0-9]*$')" + + outdated=0 + broken= + + # Check each file of each package. + for file in $files; { + if [ ! -L "$file" -a \( \ + -x "$file" -o \ + -n "$(echo "$file" | grep -E '\.so[\.0-9]*$')" \ + \) ]; then + if dependencyMissing "$file"; then + if [ -n "$raw" ]; then + statusPrint "$package_name" + break 1 + fi + fi + fi + } + ) & +} + +semaphoreCountDown jobs "Waiting for %d remaining jobs to finish." +statusSet +lockUnregisterAll + +exit 0 diff --git a/sysutils/bsdadminscripts/files/pkg_upgrade.in b/sysutils/bsdadminscripts/files/pkg_upgrade.in new file mode 100644 index 000000000000..df50669e6ed4 --- /dev/null +++ b/sysutils/bsdadminscripts/files/pkg_upgrade.in @@ -0,0 +1,2239 @@ +#!/bin/sh -f +# +# Copyright (c) 2009 +# Dominic Fandrey <kamikaze@bsdforen.de> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +readonly version=1.1 +readonly name=pkg_upgrade + +# Error table. +readonly ERR_LOCK=1 +readonly ERR_ARG=2 +readonly ERR_INDEX=3 +readonly ERR_FETCH=4 +readonly ERR_SORT=5 +readonly ERR_BACKUP_MISS=6 +readonly ERR_BACKUP_UNKNOWN=7 +readonly ERR_INSTALL=8 +readonly ERR_USER=9 +readonly ERR_TERM=10 +readonly ERR_PACKAGE_FORMAT=11 +readonly ERR_CONFLICT=12 + +# Constant assignments. +readonly logfile="%%VAR%%/log/$name.log" +readonly pid=$$ + +# Get some environment variables from uma. This includes PACKAGESITE, +# TMPDIR and PKG_INDEX. +eval "$(uma env $pid)" + +# The remote package repository, derived from PACKAGESITE. +# If this matches the PACKAGES environment variable all downloading operations +# will be omitted. +readonly packagerepos="${PACKAGESITE%/*?}" + +# Environment variables. +: ${PACKAGES="$(make -V PACKAGES -f /usr/share/mk/bsd.port.mk 2> /dev/null)"} +PACKAGES="${PACKAGES:-%%PORTS%%/packages}" +: ${PKG_DBDIR=%%VAR%%/db/pkg} +: ${TMPDIR=%%TMP%%} +: ${PKG_TMPDIR=$TMPDIR} + +# This is where backup packages will be stored. +readonly packagebackup="$PACKAGES/$name-backup" +# This is where the download manager will listen for messages. +readonly queueMessages="$TMPDIR/pkg_upgrade.messages.queue" + +# Export environment variables to ensure that every tool uses the same ones. +export ARCH PACKAGEROOT PACKAGESITE FTP_TIMEOUT PKG_INDEX +export PACKAGEROOT_MIRRORS PACKAGESITE_MIRRORS +export PACKAGES PKG_DBDIR TMPDIR PKG_TMPDIR + +# Direct index access. +readonly IDX_PKG=0 +readonly IDX_ORIGIN=1 +readonly IDX_PREFIX=2 +readonly IDX_COMMENT=3 +readonly IDX_DESCRIPTION=4 +readonly IDX_MAINTAINER=5 +readonly IDX_CATEGORIES=6 +readonly IDX_DIRECTDEPENDS=7 +readonly IDX_DEPENDS=8 +readonly IDX_WWW=9 +readonly IDX_PERLVERSION=10 +readonly IDX_PERLMODULES=11 + +# Input field seperator without spaces. +IFS=' +' + +# Parameter flags. +pAll= +pNoBackup= +pClean= +pExitOnConflict= +pForce= +pFetchOnly= +pInteractive= +pJobs= +pListDiscarded= +pNoActions= +pNoLogging= +pParanoid= +pRecursive= +pReplaceConflicts= +pMoreRecursive= +pUpwardRecursive= +pMoreUpwardRecursive= +pVerbose= + +# The categories for packages. +older= +newer= +unindexed= +multiple= +error= + +# A cache for the pkgDepends function. +dependsChecked= + +# The names of packages that do not have a verified download. +pending= + +# +# The list of packages to upgrade. +# + +# <origin>;<newPackage> +upgrade= +upgradeDepends= +upgradeDepending= + +# The <newOrgin>;<newPackage> part can also be found in $upgrade. +# <newOrigin>;<newPackage>|<oldOrigin>;<oldPackage> +replace= + +# A list of dependency substitutions for new packages. +# <originalOrigin>;<originalName>|<newDependencyOrigin>;<newDependencyName> +substituteDepends= + +# The current status line. +status= + +# The ports directory as used in the index file. +idxports= + +# +# Table Of Functions +# In order of appearance. +# +# getIndex() Fetch the latest INDEX +# getLock() Acquire a lock +# printStatus() Print status messages on the terminal +# error() Terminate with an error message +# warn() Print a warning on stderr +# verbose() Print a message, but only in verbose mode +# log() Log activity into a log file +# getIdxEscape() Escape origins and packages for regular expressions +# getIdxRows() Filter index rows with an escaped expression +# getIdxRowsEscaped() Filter index rows with an expression +# getIdxColumn() Get a certain column from index rows +# pkgAll() Make a list of outdated packages +# pkgDepends() Check dependencies +# pkgDepending() Check upwards dependencies +# pkgDependencies() Run all dependency checks +# printProgress() Print numerical progress output +# pkgSort() Sort packages by dependency +# printTask() Print the tasks to perform for a package +# pkgList() List all tasks in 'no actions' mode +# pkgDownload() Download all required packages +# pkgUpgrade() Upgrade all scheduled packages +# substituteDepends() Adjust dependencies of upgraded packages +# upgradePackage() Upgrade a given package +# identifyPackage() Identify a package by a user given string +# printHelp() Print program parameters and terminate +# readParams() Read the command line parameters +# readContents() Read the +CONTENTS of a package file +# downloadManager() Start a background download manager +# downloadManagerFetch() +# Try to fetch a package from a mirror +# downloadManagerMsgRetry() +# Tell the download manager to retry a download +# downloadManagerMsgFinished() +# Tell the download manager a download has been completed +# downloadManagerMsgRequest() +# Request a download from the download manager +# downloadManagerMsgExit() +# Tell the download manager to terminate +# validatePackage() Validate a downloaded package +# + + +# +# Update the local copy of the index and start the download manager. +# +# @param idxports +# This is set to the ports directory used in the index file. This is +# required for many index operations. If already set the index is +# assumed to be up to date and nothing is done. +# @param pVerbose +# Activate verbose output. +# +getIndex() { + # The index has already been updated. + if [ -n "$idxports" ]; then + return 0 + fi + + # Free the lock upon termination. + trap "uma unlock $pid" EXIT + + # First acquire the lock. + getLock + + verbose "Synchronize the local index copy with the package server." + + # Try to update the index. + if ! uma $pVerbose fetch ftpindex $pid; then + exit $ERR_INDEX + fi + + # Set the ports directory used in the index. + idxports="$(getIdxColumn $IDX_ORIGIN "$(head -n 1 "$PKG_INDEX")")" + idxports="${idxports%/*/*}" + + # Start the download manager. + downloadManager +} + +# +# Acquires the uma (Update Manager) lock. And spawns a process that locks +# onto PKG_DBDIR to block the ports from messing with us. +# +getLock() { + # Acquire the lock. + if ! uma lock $pid; then + if [ "$USER" != "root" ]; then + error $ERR_LOCK "The command $name has to be run as root." + else + error $ERR_LOCK "The uma (Update MAnager) lock could not be acquired, it appears the package/ports infrastructure is in use." + fi + fi + + # Lock onto PKG_DBDIR to avoid ports getting into our way. + # The ports tree locks onto PKG_DBDIR during install and deinstall. + # Since it does not use uma we use this lock to make sure the ports + # tree does not get into our way later. + if ! lockf -kst 0 "$PKG_DBDIR" sh -c "lockf -k '$PKG_DBDIR' sh -c 'while kill -0 $pid 2> /dev/null; do sleep 2; done' &"; then + error $ERR_LOCK "Locking $PKG_DBDIR failed, the ports tree might be in use." + fi +} + +# +# Prints a status message to the terminal device /dev/tty. +# +# @param 1 +# The message to print +# @param status +# The last printed message, used for clearing the status line before +# printing a new status. +# @param pClean +# If set, do not print status messages. +# +printStatus() { + test -n "$pClean" && return 0 + printf "\r%${#status}s\r%s\r" '' "$1" > /dev/tty + status="$1" +} + +# +# Exits with the given error and message on stderr. +# +# @param 1 +# The error number to exit with. +# @param 2 +# The message to exit with. +# +error() { + # Clear the status line. + printStatus + echo "$name: $2" 1>&2 + exit "$1" +} + +# +# Writes a warning message to stderr. +# +# @param 1 +# The message to write. +# +warn() { + # Clear the status line. + printStatus + echo "$name: $1" 1>&2 +} + +# +# Outputs verbose messages on stdout. +# +# @param @ +# All the parameters to be output. +# @param pVerbose +# If this is not set, do not output anything. +# +verbose() { + test -z "$pVerbose" && return 0 + echo "$@" +} + +# +# Logs the given message into a log file. +# +# The following format is used. +# +# <UTC timestamp> - <date> - (<error>|DONE): <message> +# +# UTC timestamp := The output of 'date -u '+%s' +# date := The output of 'date' +# +# @param 1 +# The error number for the log, if this is 0, the message will be +# preceded by "DONE:" instead of "ERROR($1):". +# @param 2 +# The message to log. +# @param logfile +# The name of the file to log into. +# @param pNoLogging +# If set, logging is not performed. +# +log() { + test -n "$pNoLogging" && return 0 + + if [ $1 -eq 0 ]; then + echo "$(date -u '+%s') - $(date) - DONE: $2" >> $logfile + else + echo "$(date -u '+%s') - $(date) - ERROR($1): $2" >> $logfile + fi +} + +# +# An escape function for package names fed to the getIdxColumn function. +# This function reads from the standard input unless a file is named +# in the parameters. +# Note that the escaping is done for extended regular expressions, however +# only characters that can appear in package names are escaped. +# +# @param @ +# More parameters can be added to the sed command. +# +getIdxEscape() { + sed -E -e 's/([+.])/\\\1/g' "$@" +} + +# +# Outputs all rows of the index that match a given pattern in a column. +# The pattern should not match '|'. +# +# @param 1 +# The column that has to match the pattern. +# @param 2 +# The pattern that has to be matched, an extended regular expression. +# @param 3 +# Optional, the rows to match against instead of using the index file. +# +getIdxRows() { + if [ -z "$3" ]; then + grep -E "^([^|]*\|){$1}($2)(\|.*)?\$" "$PKG_INDEX" + else + echo "$3" | grep -E "^([^|]*\|){$1}($2)(\|.*)?\$" + fi +} + +# +# Outputs all rows of the index that match a given string. +# The string should not contain '|'. +# +# @param 1 +# The column that has to match the string. +# @param 2 +# The string that has to be matched. +# @param 3 +# Optional, the rows to match against instead of using the index file. +# +getIdxRowsEscaped() { + getIdxRows $1 "$(echo "$2" | getIdxEscape)" "$3" +} + +# +# Outputs a column of each index row piped into it. +# +# @param 1 +# The column to output. +# @param 2 +# The rows to output the columns from. +# +getIdxColumn() { + echo "$2" | sed -E "s,^([^|]*\|){$1}([^|]*)\|.*,\2,1" +} + +# +# Stores all the packages not in sync with the index file in categories. +# +# @param older +# The list of packages older than those in the index. +# @param newer +# The list of packages newer than those in the index. +# @param unindexed +# The list of packages not in the index. +# @param multiple +# The list of packages that have multiple index entries. +# @param error +# The list of packages with broken package database entries. +# @param pForce +# If set, register all installed packages in the index as outdated. +# @param pAll +# If set, add all outdated packages to the list of packages to upgrade. +# @param pListDiscarded +# If set, list all the packages that are ignored. +# @param upgrade +# The list to add packages to if pAll is set. +# +pkgAll() { + local package pkgname origin operator row discarded + + # There's nothing to be done if all of the following conditions are + # met: + # - Nothing is yet listed for upgrading, so we do not need a list + # of outdated packages for dependency checking. + # - The updating of all packages is not requested. + # - The listing of ignored (i.e. not indexed) packages is not + # requested. + test -z "$upgrade" -a -z "$pAll" -a -z "$pListDiscarded" && return 0 + + verbose "Make a list of outdated packages." + + printStatus "Reading version information of installed packages ..." + + if [ -n "$pForce" ]; then + # In force mode it is assumed that all installed packages to + # be found in the index are outdated. + for package in $(pkg_version -Io "${PKG_INDEX}"); { + origin="${package%% *}" + row="$(getIdxRowsEscaped $IDX_ORIGIN "$idxports/$origin")" + pkgname="$(getIdxColumn $IDX_PKG "$row")" + printStatus "Checking <$pkgname>." + operator="${package##* }" + case "$operator" in + '?') + unindexed="$unindexed${unindexed:+$IFS}$origin" + ;; + '!') + error="$error${error:+$IFS}$origin" + ;; + *) + older="$older${older:+$IFS}$origin;$pkgname" + ;; + esac + } + else + # Categorize installed packages and their relations to the + # index. + for package in $(pkg_version -IoL = ${PKG_INDEX}); { + origin="${package%% *}" + row="$(getIdxRowsEscaped $IDX_ORIGIN "$idxports/$origin")" + pkgname="$(getIdxColumn $IDX_PKG "$row")" + printStatus "Checking <${pkgname:-$(pkg_info -qO $origin)}>." + operator="${package##* }" + case "$operator" in + '<') + older="$older${older:+$IFS}$origin;$pkgname" + ;; + '>') + newer="$newer${newer:+$IFS}$origin;$pkgname" + ;; + '?') + unindexed="$unindexed${unindexed:+$IFS}$origin" + ;; + '*') + multiple="$multiple${multiple:+$IFS}$origin" + ;; + '!') + error="$error${error:+$IFS}$origin" + ;; + esac + } + fi + + printStatus "Assemble checked packages ..." + + # Remove packages to upgrade from the list of outdated packages. + for package in $upgrade; { + older="$(echo "$older" | grep -vx "$package")" + } + + # Append outdated packages to the list of packages to update if all + # packages are to be updated. + if [ -n "$pAll" ]; then + downloadManagerMsgRequest "$older" + upgrade="$upgrade${older:+${upgrade:+$IFS}}$older" + older= + fi + + # Clear the status line. + printStatus + + # Print the discarded packages. + if [ -n "$pListDiscarded" ]; then + verbose "List discarded packages." + + discarded="$unindexed$IFS$multipleIFS$error" + discarded="$(echo "$discarded" | grep -vFx '' | sort -u)" + + test -n "$discarded" && echo "$discarded" + fi +} + +# +# Adds all missing dependencies to the list of packages to upgrade. +# +# @param 1 +# This is used to check the dependencies of newly added depending +# packages. +# @param upgrade +# The primary list of packages to upgrade (read only). +# @param upgradeDepends +# The list to add packages to upgrade to. +# @param older +# The list of outdated packages. Packages for upgrading are removed from +# it. +# @param dependsChecked +# A list of already checked dependencies, to avoid double checks. +# @param pRecursive +# If set, also add outdated dependencies to the upgrade list. +# @param pMoreRecursive +# If set, also update the dependencies of depending packages. +# @param pForce +# If set together with pRecursive, add all dependencies to the upgrade +# list. +# +pkgDepends() { + local pkgname package row rows depends origin escapedPkg upgradeList + + printStatus "Preparing dependency checks ..." + + # In thorough mode the depencies of depending packages are updated, too. + upgradeList="${1:-$upgrade}" + + # Luckily packages know their indirect dependencies, too. This way + # it is not necessary to check for dependencies recursively. + depends= + for package in $upgradeList; { + row="$(getIdxRowsEscaped $IDX_ORIGIN "$idxports/${package%;*}")" + row="$(getIdxColumn $IDX_DEPENDS "$row")" + depends="$depends${depends:+${row:+ }}$row" + } + + # Reformat depends and throw out duplicates. + depends="$( + echo "$depends" | sed "s/ /\\$IFS/g" | sort -u + )" + + # Do some prefiltering. + rows="$(getIdxRowsEscaped $IDX_PKG "$(echo "$depends" | rs -TC\|)")" + + # Check for missing or outdated dependencies. + for pkgname in $depends; { + escapedPkg="$(echo "$pkgname" | getIdxEscape)" + + # Skip packages already checked. + if echo "$dependsChecked" | grep -qFx "$pkgname"; then + continue + fi + dependsChecked="$dependsChecked${dependsChecked:+$IFS}$pkgname" + + printStatus "Check dependency <$pkgname>." + + # Skip this if this package is already scheduled for updating. + if echo "$upgrade${upgradeDepending:+$IFS$upgradeDepending}" | grep -qF ";$pkgname"; then + continue + fi + + row="$(getIdxRows $IDX_PKG "$escapedPkg" "$rows")" + + # If this package could not be identified this is an index + # incosistency, that can only be ignored. + if [ -z "$row" ]; then + warn "Ignore index inconsistency, the dependency <$pkgname> is not in the index." 1>&2 + continue + fi + + origin="$(getIdxColumn $IDX_ORIGIN "$row")" + origin="${origin#$idxports/}" + package="$origin;$(getIdxColumn $IDX_PKG "$row")" + + # + # Deal with dependencies according to set parameters. + # + if [ -z "$(pkg_info -qO "$origin")" ]; then + # The depency is not installed. + upgradeDepends="$upgradeDepends${upgradeDepends:+$IFS}$package" + # Request a package download. + downloadManagerMsgRequest "$package" + elif [ -n "$pMoreRecursive" -o -n "$pRecursive" -a -z "$1" ]; then + # Check whether the dependency is outdated. + if echo "$older" | grep -qFx "$package"; then + upgradeDepends="$upgradeDepends${upgradeDepends:+$IFS}$package" + older="$(echo "$older" | grep -vFx "$package")" + # Request a package download. + downloadManagerMsgRequest "$package" + fi + fi + } +} + +# +# Checks whether packages depending on the packages to update require updating. +# +# @param 1 +# This is used to check the depending packages of newly added +# dependencies. +# @param older +# The list of outdated packages. If pForce is set, this includes all +# installed packages listed in the index. +# @param upgrade +# The primary list of packages to upgrade (read only). +# @param upgradeDepending +# The list of depending packages to upgrade. +# @param pUpwardRecursive +# If not set nothing is done. +# @param pMoreUpwardRecursive +# Also check the depending packages of depencencies. +# @param pAll +# If this is set do nothing. +# +pkgDepending() { + # Without the upwardRecursive option this is completely + # unnecessary. + if [ -z "$pUpwardRecursive" ]; then + return 0 + fi + + # If all packages are already going to be upgraded, there is no + # need for this. + if [ -n "$pAll" ]; then + return 0 + fi + + # Only update depending packages of dependencies in thorough mode. + if [ -n "$1" -a -z "$pMoreUpwardRecursive" ]; then + return 0 + fi + + local package pkgname origin row depends escapedPkg upgradeList + + printStatus "Preparing upwards dependency checks ..." + + # In thorough mode the depencies of depending packages are updated, too. + upgradeList="${1:-$upgrade}" + + # Do some prefiltering. + rows="$(getIdxRowsEscaped $IDX_ORIGIN "$( + echo "$older" | rs -TC\| | sed -E "s'([^;|]*);[^|]*'$idxports/\1'g" + )")" + + # For each outdated package, check whether it depends on a package + # to upgrade. In force mode outdated packages are all packages, so + # the difference does not have to be made here. + for package in $older; { + # Skip this if this package is already scheduled for updating. + if echo "$upgrade${upgradeDepends:+$IFS$upgradeDepends}${upgradeDepending:+$IFS$upgradeDepending}" | grep -qFx "$package"; then + continue + fi + + printStatus "Check for upwards dependency <${package#*;}>." + + origin="${package%;*}" + row="$(getIdxRowsEscaped $IDX_ORIGIN "$idxports/$origin" "$rows")" + + # Ignore unindexed packages. + if [ -z "$row" ]; then + continue + fi + + depends="$(getIdxColumn $IDX_DEPENDS "$row")" + + # It has no dependencies, so it cannot depend on anything + # in the upgrade list. + if [ -z "$depends" ]; then + continue + fi + + # Reformat dependencies. + depends="$(echo "$depends" | sed -Ee "s/([^ ]+)/;\1/g" -e "s/ /\\$IFS/g")" + + # Check every dependency for matching the upgrade packages. + if echo "$upgradeList" | grep -qF "$depends"; then + upgradeDepending="$upgradeDepending${upgradeDepending:+$IFS}$package" + older="$(echo "$older" | grep -vFx "$package")" + downloadManagerMsgRequest "$package" + fi + } +} + +# +# This function calls pkgDepending and pkgDepends until no new packages +# show up for updating. All the clever stuff happens in those functions. +# +# @param upgrade +# The list of packages to upgrade. +# @param upgradeDepends +# The list of dependencies to add to the list of packages to upgrade. +# @param upgradeDepending +# The list of depending packages to add to the list of packages +# to upgrade. +# +pkgDependencies() { + test -z "$upgrade" && return 0 + + verbose "Perform dependency checks." + + # Run the primary dependency checks. + pkgDepending + downloadManagerMsgRequest "$upgradeDepending" + pkgDepends + downloadManagerMsgRequest "$upgradeDepends" + + # The idea is to keep on checking until nothing new shows up. + # Whether that is the case depends on the level of recursiveness. + while [ -n "$upgradeDepends$upgradeDepending" ]; do + if [ -n "$upgradeDepends" ]; then + # Deal with packages depending on the updated packages. + pkgDepending "$upgradeDepends" + upgrade="$upgradeDepends$IFS$upgrade" + upgradeDepends= + fi + + if [ -n "$upgradeDepending" ]; then + # Deal with missing or outdated dependencies. + pkgDepends "$upgradeDepending" + upgrade="$upgrade$IFS$upgradeDepending" + upgradeDepending= + fi + done + + # Clear the status line. + printStatus +} + +# +# Prints a progress message to the terminal device /dev/tty. +# +# @param 1 +# Total amount of operations to do. +# @param 2 +# The amount of operations performed. +# @param 3 +# The name of the package that is currently operated on. +# @param 4 +# The text prepending the progress information. +# @param status +# The last printed message, used for clearing the status line before +# printing a new status. +# @param pClean +# If set, do not print progress messages. +# +printProgress() { + test -n "$pClean" && return 0 + printf "\r%${#status}s\r$4 %${#1}s of %${#1}s (%3s%%) <$3>.\r" '' "$2" "$1" "$(($2 * 100 / $1))" > /dev/tty + status="$4 $1 of $1 (100%) <$3>." +} + +# +# Sorts the packages to upgrade by dependency. +# +# The trick is to have a list of already sorted packages. Each package added +# to the list is inserted right behind its last dependency already present +# there. +# Packages without any dependencies in the sorted list are prepended. This +# way it is ensured that they end up before all already sorted packages +# that depend on them, without additional checking. +# +# @param upgrade +# The list of packages to sort. +# @param pParanoid +# If set, make cyclic dependency checks. +# +pkgSort() { + local rows sorted package row depends dependency pkgname + local totalCount count + + test -z "$upgrade" && return 0 + + verbose "Sort packages by dependency." + + printStatus "Prepare sorting of packages ..." + + # Limit rows to whatever is currently required. + rows="$(getIdxRowsEscaped $IDX_ORIGIN "$( + echo "$upgrade" | getIdxEscape -e 's/;.*//1' -e "s,^,$idxports/,1" | rs -TC\| + )")" + + # The number of packages + totalCount=$(($(echo "$upgrade" | wc -l))) + count=0 + + # Sort each package into the list of sorted packages. + sorted= + for package in $upgrade; { + count=$(($count + 1)) + pkgname="${package#*;}" + printProgress $totalCount $count "$pkgname" 'Sort' + + # Get the list of dependencies that should be updated before + # the current package. + row="$(getIdxRowsEscaped $IDX_PKG "$pkgname" "$rows")" + depends="$(getIdxColumn $IDX_DEPENDS "$row" | sed -E "s/ /\\$IFS/g")" + + # Get the last matching dependency in the list. + dependency="$(echo "$sorted" | grep -Fx "$depends" | tail -n 1)" + + # If there is no match, just prepend to the list. + if [ -z "$dependency" ]; then + sorted="$pkgname${sorted:+$IFS$sorted}" + continue + fi + + # Insert right behind the match. + dependency="$(echo "$dependency" | getIdxEscape)" + sorted="$(echo "$sorted" | sed -E "s/^$dependency$/$dependency\\$IFS$pkgname/1")" + } + + # Perform optional cyclic dependency check. + if [ -n "$pParanoid" ]; then + printStatus "Validate sorting order ..." + + # Validate the sort order. + count=0 + for pkgname in $sorted; { + count=$(($count + 1)) + printProgress $totalCount $count "$pkgname" 'Validate' + + # Get the list of dependencies that should be updated before + # the current package. + row="$(getIdxRowsEscaped $IDX_PKG "$pkgname" "$rows")" + depends="$(getIdxColumn $IDX_DEPENDS "$row" | sed -E "s/ /\\$IFS/g")" + + # Append the package to the list of dependencies to match. + depends="${depends:+$depends$IFS}$pkgname" + + # Get the last match in the list. + dependency="$(echo "$sorted" | grep -Fx "$depends" | tail -n 1)" + # The last match has to be the package. + if [ "$dependency" != "$pkgname" ]; then + error $ERR_SORT "The package <$pkgname> was not sorted properly, a likely cause is a circular dependency." + fi + } + fi + + printStatus "Assemble sorted packages ..." + + # Replace package names with <origin>;<package> pairs. + for package in $upgrade; { + pkgname="$(echo "${package#*;}" | getIdxEscape)" + sorted="$(echo "$sorted" | sed -E "s'^$pkgname\$'$package'1")" + } + + upgrade="$sorted" + printStatus +} + +# +# Prints the update/replace/install task. +# +# @param 1 +# The package to upgrade/install. +# @param replace +# The list of packages to replace. +# +printTask() { + local package newPkgname newOrigin oldPkgname oldOrigin + + # Get the name and origin of the new package. + newPkgname="${1#*;}" + newOrigin="${1%;*}" + + # Look for a package the new one replaces. + package="$(echo "$replace" | grep -F "$1|")" + + # Look for a package this one replaces. + # The current package actually replaces another one. + if [ -n "$package" ]; then + # Get the name and origin of the old package. + package="${package#*|}" + oldPkgname="${package#*;}" + oldOrigin="${package%;*}" + + echo "Replace <$oldPkgname> ($oldOrigin) with <$newPkgname> ($newOrigin)" + return 0 + fi + + # Check whether there's an old version of this package around. + package="$(pkg_info -qO "$newOrigin")" + + # An older package with this origin is installed. + if [ -n "$package" ]; then + echo "Update <$package> to <$newPkgname> ($newOrigin)" + return 0 + fi + + # Aparently this package will be newly installed. + echo "Install <$newPkgname> ($newOrigin)" +} + +# +# List the packages that are going to be upgraded, installed and replaced. +# If the 'no actions' mode is active. +# +# @param upgrade +# The list of packages to upgrade. +# @param pNoActions +# Print the list of tasks. +# +pkgList() { + # Only list packages in "no actions" mode. + test -z "$pNoActions" && return 0 + + test -z "$upgrade" && return 0 + + local package + + verbose "The following packages will be updated:" + + for package in $upgrade; { + printTask "$package" + } +} + +# +# Wait for downloaded packages and validate them. +# +# @param upgrade +# The list of packages to download. +# @param pending +# The list of pending downloads. +# @param packagerepos +# The location of the remote package repository (derived from +# PACKAGESITE). If this is identical with the local repository, +# the download manager was not started. +# @param pNoActions +# Do not download anything. +# +pkgDownload() { + test -n "$pNoActions" && return 0 + + test -z "$upgrade" && return 0 + + local package total count line + + verbose "Validate downloaded packages." + + printStatus "Waiting for downloads ..." + + # Create a list of the package names to validate. + # Entries are removed from this list by validatePackage(). + pending="$(echo "$upgrade" | sed 's/.*;//1')" + + # The total number of packages to validate. + total="$(($(echo "$upgrade" | wc -l)))" + + # Check whether the download manager is available. + if [ "$PACKAGES" = "$packagerepos" ]; then + # + # The local repository is identical with the remote repository + # so the assumption is all packages should already be there. + # + + # Validate all packages. + for package in $pending; { + count=$(($count + 1)) + printProgress $total $count "$package" "Validate" + validatePackage "$package" + } + else + # + # The download manager is available, so hang on to its message + # queue and proceed with validating as packages are finished. + # + count=0 + + while [ -n "$pending" ]; do + read line + case "$line" in + finished:*) + count=$(($count + 1)) + package="${line##*;}" + printProgress $total $count "$package" "Validate" + validatePackage "$package" + ;; + esac + done < "$queueMessages" + + # Stop the download manager. + downloadManagerMsgExit + fi + + # Clear the status line. + printStatus +} + +# +# Upgrade each package. +# +# @param upgrade +# The list of packages to upgrade. +# @param conflictReplace +# This list is reset for conflict handling. +# @param pNoActions +# Do not update anything. +# @param pFetchOnly +# Do not update anything. +# +pkgUpgrade() { + test -n "$pNoActions" -o -n "$pFetchOnly" && return 0 + + test -z "$upgrade" && return 0 + + local package + + verbose "Install $(($(echo "$upgrade" | wc -l))) package(s)." + + for package in $upgrade; { + upgradePackage "$package" + } +} + +# +# To handle conflicts this function removes dependencies from a given package +# and appends one or more new ones to take their place. Also the +REQUIRED_BY +# files of the appended dependencies are updated. +# +# @param 1 +# The name of the package to which to apply the substitutions. +# @param substituteDepends +# The list of dependency substitutions that should take place. +# +substituteDepends() { + # End here if there's nothing to substitute. + test -z "$substituteDepends" && return 0 + + local line originalOrigin originalPkgname newOrigin newPkgname + local contents append remove requiredBy + + printStatus "Adjust the dependencies of <$1> ..." + + # Get the contents file. + contents="$(cat "$PKG_DBDIR/$1/+CONTENTS")" + + # Because there can be several substitutions for a single package + # the new ones will be added to the end of the +CONTENTS file and all + # the matches will be removed later. + append= + remove= + for line in $substituteDepends; { + # Get original origin and package name from the line. + originalOrigin="${line%%;*}" + originalPkgname="${line%|*}" + originalPkgname="${originalPkgname#*;}" + + # Continue with the next line if this one does not match. + if ! echo "$contents" | grep -qFx "@pkgdep $originalPkgname"; then + continue + fi + + # Get new origin and package name from the line. + newOrigin="${line#*|}" + newPkgname="${newOrigin#*;}" + newOrigin="${newOrigin%;*}" + + warn "Add dependency <$newPkgname> ($newOrigin)." + + # Remember what to append and what to remove. + remove="${remove:+$remove$IFS}@pkgdep $originalPkgname$IFS@comment DEPORIGIN:$originalOrigin" + # Just for the very unlikely case that two dependencies get + # replaced for conflicting with the same package, check that + # a dependency is not added twice. + if ! echo "$append" | grep -qFx "@pkgdep $newPkgname"; then + append="$append$IFS@pkgdep $newPkgname$IFS@comment DEPORIGIN:$newOrigin" + fi + + # Make an entry for the package in the +REQUIRED_BY file of + # of the dependency to append. + requiredBy="$(cat "$PKG_DBDIR/$newPkgname/+REQUIRED_BY" 2> /dev/null)" + requiredBy="${requiredBy:+$requiredBy$IFS}$1" + echo "$requiredBy" | sort -u > "$PKG_DBDIR/$newPkgname/+REQUIRED_BY" + } + + # Remove the original dependency entries. + contents="$(echo "$contents" | grep -vFx "$remove")" + # Write the new file. Note that $append always starts with a newline. + echo "$contents$append" > "$PKG_DBDIR/$1/+CONTENTS" +} + +# +# Install the given package. This is where the magic happens. +# +# @param replace +# The list of packages to replace (read only). +# @param substituteDepends +# A list of dependency substitutions that should take place for each +# newly installed package to resolve conflicting packages. +# @param packagebackup +# The location for backup packages. This is derived from PACKAGES. +# @param pNoBackup +# If set, delete backups after successful completion. +# +upgradePackage() { + local task targetPackage targetPkgname targetOrigin package replace + local escapedPkg removePackages origin file conflict conflicting + local replacePkgdep requiredBy count + local signal + + # Get a string with the current upgrade task. + task="$(printTask "$1")" + echo "===> $task" + + targetPackage="$1" + targetPkgname="${1#*;}" + targetOrigin="${1%;*}" + + printStatus "Prepare installation of <$targetPkgname> ..." + + # Get the packages to replace with this one. Several packages can be + # replaced with a single one. + escapedPkg="$(echo "$targetPackage" | getIdxEscape)" + replace="$(echo "$replace" | grep -Ex "$escapedPkg\|.*" | sed -E "s'^$escapedPkg\|''1")" + + # Append the current package to the list of packages to replace. + replace="${replace:+$replace$IFS}$targetPackage" + + # Create the list of outdated packages that have to be backed up + # and for which pkgdb adjustments have to be made after successful + # installation of the new package. + # Also create the necessary sed expressions to update the + # package database. + removePackages= + replacePkgdep= + for package in $replace; { + origin="${package%;*}" + package="$(pkg_info -qO "$origin")" + test -z "$package" && continue + removePackages="$removePackages${removePackages:+$IFS}$package" + package="$(echo "$package" | getIdxEscape)" + replacePkgdep="$replacePkgdep -e 's|^@pkgdep $package\$|@pkgdep $targetPkgname|1'" + if [ "$origin" != "$targetOrigin" ]; then + replacePkgdep="$replacePkgdep -e 's|^@comment DEPORIGIN: $origin\$|@comment DEPORIGIN:$targetOrigin|1'" + fi + + } + + # Get a list of conflicting packages. The conflicts list is + # provided by readContents(). + readContents "$PACKAGES/All/$targetPkgname.tbz" + conflicting= + for conflict in $conflicts; { + # Match the conflict pattern against installed packages. + for conflict in $(pkg_info -E "$conflict"); { + escapedPkg="$(echo "$conflict" | getIdxEscape)" + # Only add to the conflicting list if the conflicting + # package is not in the list of packages to replace. + if ! echo "$removePackages" | grep -qEx "$escapedPkg"; then + conflicting="${conflicting:+$conflicting$IFS}$conflict" + fi + } + } + # Remove duplicated entries. + conflicting="$(echo "$conflicting" | sort -u)" + + # Check whether any conflicts were found. + if [ -n "$conflicting" ]; then + # What happens now depends on the user preferences. + if [ -n "$pExitOnConflict" ]; then + # The user has chosen to bail out when a conflict + # occurs. + log $ERR_CONFLICT "$task" + error $ERR_CONFLICT "The package <$targetPkgname> conflicts with the following packages:$IFS$conflicting" + elif [ -n "$pReplaceConflicts" ]; then + # The user has chosen that conflicting packages should + # be replaced as if they were explicitly listed for + # replacing. + conflicts= + for package in $conflicting; { + warn "The package <$package> conflicts with <$targetPkgname> and will be replaced." + removePackages="$removePackages${removePackages:+$IFS}$package" + origin="$(pkg_info -qo "$package")" + # The next line is just for prettier log output. + conflicts="${conflicts:+$conflicts, }<$package> ($origin)" + package="$(echo "$package" | getIdxEscape)" + replacePkgdep="$replacePkgdep -e 's|^@pkgdep $package\$|@pkgdep $targetPkgname|1'" + if [ "$origin" != "$targetOrigin" ]; then + replacePkgdep="$replacePkgdep -e 's|^@comment DEPORIGIN: $origin\$|@comment DEPORIGIN:$targetOrigin|1'" + fi + } + log 0 "Conflict <$targetPkgname> ($targetOrigin) remove package(s) $conflicts" + else + # The default action is to assume that the conflicting + # packages fulfill the required functionality. + conflicts= + for package in $conflicting; { + warn "The package <$targetPkgname> will not be installed in favour of <$package>, because they conflict." + origin="$(pkg_info -qo "$package")" + # Record the necessary substitutions. + # TODO: Later versions will have to store this + # for resume. + substituteDepends="${substituteDepends:+$substituteDepends$IFS}$targetPackage|$origin;$package" + # This is just for prettier log output. + conflicts="${conflicts:+$conflicts, }<$package> ($origin)" + } + # Log the conflict resolution. + log 0 "Conflict <$targetPkgname> ($targetOrigin) favour package(s) $conflicts" + # Skip to the next package. + return 0 + fi + fi + + # Backup packages. + mkdir -p "$packagebackup" + for package in $removePackages; { + printStatus "Backup <$package>." + pkg_create -b "$package" "$packagebackup/$package" + case $? in + 0) + # Everything went well. + ;; + 1) + # If this happens someone's been messing with + # the packages just milliseconds ago. + log $ERR_BACKUP_MISS "$task" + error $ERR_BACKUP_MISS "The backup of <$package> failed. The package is missing." + ;; + 2) + # Fortunately pkg_create backs up as much as + # as is possible. That the backup (and hence + # the present package) is incomplete is all + # the more reason to upgrade. + # I do not understand why portmaster is + # interactive in this case. + warn "Ignoring incomplete backup of <$package>." + ;; + *) + # Well, I've got no idea at all what else + # could go wrong. Too bad the return codes + # of pkg_create are not documented. + log $ERR_BACKUP_UNKNOWN "$task" + error $ERR_BACKUP_UNKNOWN "The backup of <$package> failed for unknown reasons." + ;; + esac + } + + # Block SIGINT (CTRL-C), because that would really wrack havoc upon + # the package database in the following section. + signal= + trap "signal=$ERR_USER" sigint + trap "signal=$ERR_TERM" sigterm + + # Delete packages. + requiredBy= + count=-1 + for package in $removePackages; { + printStatus "Delete <$package>." + # Remember +REQUIRED_BY contents for roll-back. + count=$(($count + 1)) + local "requiredBy$count" + setvar "requiredBy$count" "$(cat "$PKG_DBDIR/$package/+REQUIRED_BY" 2> /dev/null)" + # Remember +REQUIRED_BY contents for the new package. + requiredBy="${requiredBy:+$requiredBy$IFS}$(cat "$PKG_DBDIR/$package/+REQUIRED_BY" 2> /dev/null)" + # Finally delete the package. + pkg_delete -f "$package" + } + + # Update the package database. + printStatus "Update package database for <$targetPkgname>." + if [ -n "$replacePkgdep" ]; then + for file in $(find "$PKG_DBDIR" -name '+CONTENTS'); { + eval "sed -Ei '.$name' $replacePkgdep '$file'" + } + fi + + # If an old version of this package was favoured in a conflict, + # the substituteDepends list has to be changed. + substituteDepends="$(echo "$substituteDepends" | sed "s'\|$targetOrigin;.*'|$targetPackage'1")" + + # Try to install the new package. + printStatus "Install <$targetPkgname>." + if ! env PKG_PATH="$PACKAGES/All" pkg_add -f "$targetPkgname"; then + # Installation went wrong, roll back! + printStatus "Roll back changes for <$targetPkgname>." + for file in $(find "$PKG_DBDIR" -name "*.$name"); { + mv -f "$file" "${file%.$name}" + } + count=-1 + for package in $removePackages; { + # Restore package. + env PKG_PATH="$packagebackup" pkg_add -f "$package" + # Recover +REQUIRED_BY file. + count=$(($count + 1)) + eval "echo \"\$requiredBy$count\"" > "$PKG_DBDIR/$package/+REQUIRED_BY" + # Remove the backup if set. + test -n "$pNoBackup" && rm "$packagebackup/$package.tbz" + } + log $ERR_INSTALL "$task" + error $ERR_INSTALL "The installation of <$targetPkgname> failed." + fi + + # Add the +REQUIRED_BY contents of all deleted packages to the + # +REQUIRED_BY file of the new one. + requiredBy="$(echo "$(cat "$PKG_DBDIR/$targetPkgname/+REQUIRED_BY" 2> /dev/null)$IFS$requiredBy" | grep -vFx '' | sort -u)" + echo "$requiredBy" > "$PKG_DBDIR/$targetPkgname/+REQUIRED_BY" + + # Make dependency substitutions from conflict resolving. + substituteDepends "$targetPkgname" + + # Log successful completion of the task. + log 0 "$task" + + # Remove backups if set. + if [ -n "$pNoBackup" ]; then + for package in $removePackages; { + printStatus "Remove backup of <$package>." + rm "$packagebackup/$package.tbz" + } + fi + + # Remove package database backups. + # TODO: Later versions will instead store them to allow a rollback. + printStatus "Remove database backups for <$targetPkgname>." + find "$PKG_DBDIR" -name "*.$name" -exec rm \{\} \; + + # Clear the status line. + printStatus + echo "=> $task succeeded" + + # Bail out if SIGINT or SIGTERM were encountered. + if [ -n "$signal" ]; then + error $signal "The process was interrupted." + fi + + # Reactivate default signal handlers. + trap - sigint sigterm +} + +# +# Identify the package by a given string. Outputs the origin of all matched +# packages, as well as the package name of the newest available package. +# The output is in the following shape: +# <origin>;<package> +# +# The shell wildcards '*' and '?' are supported. +# Origin and package names with wildcards are matched against installed +# packages. Unambiguous package names and origins are matched against the +# index. +# +# @param 1 +# The package identifier to find matches for. +# +identifyPackage() { + local packages package mangledPackage rows matchingRows mangledRows + local origins origin guess escapedPkg + + # Check for wildcards. + guess= + if echo "$1" | grep -qE '\*|\?|\[.*]'; then + guess=1 + fi + package="$1" + + # Distuinguish between origins and packages. + case "$package" in + */*) + # An origin has been given. + if [ -n "$guess" ]; then + # Wildcards present, match against installed + # packages. + + # Get all matching packages. + packages="$(pkg_info -qO "$package")" + + # Convert for use in a regular expression. + package="$(echo "$package" | getIdxEscape -e 's/\*/[^|]*/g' -e 's/\?/[^|]/g')" + # Get rows matching the given package origin. + # This is a performance tweak, so the whole + # index will not have to be parsed in the + # following output loop. + rows="$(getIdxRows $IDX_ORIGIN "$idxports/$package")" + + # Output all matching packages. + for package in $packages; { + # Get the origin. + origin="$(pkg_info -qo "$package")" + + # Match this package origin against the + # previously filtered rows. + package="$(getIdxRowsEscaped $IDX_ORIGIN "$idxports/$origin" "$rows")" + # Get the package name of the newest + # package from the index. + package="$(getIdxColumn $IDX_PKG "$package")" + # Output origin/package pair. + echo "$origin;$package" + } + + # If no matches have been found, terminate. + if [ -z "$packages" ]; then + error $ERR_ARG "Package origin <$package> not matched by any installed package!" 1>&2 + fi + else + # There is an unambigious origin, match it + # against the index. + origin="$package" + # Get the index row. + rows="$(getIdxRowsEscaped $IDX_ORIGIN "$idxports/$origin")" + # Get the package name column. + package="$(getIdxColumn $IDX_PKG "$rows")" + # Output origin/package pair, if a package for + # the given origin was found. + if [ -n "$package" ]; then + # Output origin/package pair. + echo "$origin;$package" + else + error $ERR_ARG "Package origin <$origin> not in index!" 1>&2 + fi + fi + ;; + *) + # A package name has been given. + if [ -n "$guess" ]; then + # Wildcards present, match against installed + # packages. + + # Get the origins of matching packages. + origins="$(pkg_info -qo "$package")" + + # Prepare the package name for use in a + # regular expression. + package="$(echo "$package" | getIdxEscape -e 's/\*/[^|]*/g' -e 's/\?/[^|]/g')" + # Get rows matching the given package name. + # This is a performance tweak, so the whole + # index will not have to be parsed in the + # following output loop. + rows="$(getIdxRows $IDX_PKG "$package")" + # Output all matching packages. + for origin in $origins; { + # Get the index row for this origin. + package="$(getIdxRowsEscaped $IDX_ORIGIN "$idxports/$origin" "$rows")" + # Get the latest package name from the + # index. + package="$(getIdxColumn $IDX_PKG "$package")" + # Output origin/package pair. + echo "$origin;$package" + } + + # If no matches have been found, terminate. + if [ -z "$origins" ]; then + error $ERR_ARG "Package identifier <$package> not matched!" 1>&2 + fi + else + # A package name without wildcards has been + # given. This is expected to either be an exact + # package name or a LATEST_LINK name. + + # TODO: This would be much better if + # LATEST_LINK was known. This is information + # simply missing in the index. + # To make up for this some guessing is done in + # case of no matches or more than one match. + # But this fails for apache13 and probably + # other packages as well. + + # First try whether it is the current version + # of a package. + origin="$(pkg_info -qo "$package" 2> /dev/null)" + if [ -n "$origin" ]; then + # Get the matching index rows. + rows="$(getIdxRowsEscaped $IDX_ORIGIN "$idxports/$origin")" + fi + + # If it's not a current version, match against + # the index. + if [ -z "$rows" ]; then + # Get the matching rows. This should be + # only one, but it won't be for ports + # that define a proprietary LATEST_LINK. + escapedPkg="$(echo "$package" | getIdxEscape)" + rows="$(getIdxRows $IDX_PKG "$escapedPkg(-[^-]+)?")" + fi + + # No match, start some guessing. + # This fails for packages with a version tail, + # which is just what is wanted. + if [ -z "$rows" ]; then + # Assume this is a LATEST_LINK kind + # package name and remove the trailing + # numbers. + mangledPackage="$(echo "$package" | sed -E 's/[0-9]+$//1')" + # Get the matching rows, this is likely + # to be too many (i.e. more than one). + rows="$(getIdxRows $IDX_PKG "$mangledPackage-[^-]+")" + fi + + # If there is more than one matching row, + # try to match against the origin. + if [ "$(($(echo "$rows" | wc -l)))" -gt "1" ]; then + # Match against the origin. + rows="$(getIdxRows $IDX_ORIGIN "[^|]*/$package" "$rows")" + + # If there is still more than one + # match, match against the origins + # of existing packages. + if [ "$(($(echo "$rows" | wc -l)))" -gt "1" ]; then + for origin in $(getIdxColumn $IDX_ORIGIN "$rows"); { + test -n "$(pkg_info -qO "$origin")" \ + && matchingRows="$matchingRows${matchingRows:+$IFS}$(getIdxRowsEscaped $IDX_ORIGIN "$origin" "$rows")" + } + rows="$matchingRows" + fi + + # Either a single origin is matched or + # it's time to bail out and give up. + if [ "$(($(echo "$rows" | wc -l)))" -ne "1" ]; then + # The wrong amount of matches + # has occured. Bail out. + error $ERR_ARG "Package identifier <$package> not unambiguously matched!" 1>&2 + fi + fi + + # Output if a package has been matched. + if [ -n "$rows" ]; then + # Get the origin of the given package. + origin="$(getIdxColumn $IDX_ORIGIN "$rows")" + # Geth the package name. + package="$(getIdxColumn $IDX_PKG "$rows")" + # Output origin/package pair. + echo "${origin#$idxports/};$package" + else + error $ERR_ARG "Package identifier <$package> not in index!" 1>&2 + fi + fi + ;; + esac +} + +# +# Prints the parameter list and terminates the program. +# +printHelp() { + printf "$name v$version +usage: + $name -h + $name -a [-b] [-bcCdfFlnpvX] [-o new existing] [update] [install] + $name [-bcCdfFlnpvX] [-r [-r]] [-R [-R]] [-o new existing] + %${#name}s [update] [install]\n" '' + exit 0 +} + +# +# Parse the command line parameters. +# +# @param upgrade +# A list of packages to upgrade. +# @param depth +# This is used by the function to store the recursion depth and +# should be unset when calling it. +# @param origin +# This is used by the function across differtent recursion depths to +# remember whether a package origin is expected. +# @param pAll +# Is set if all packages should be update. +# @param pNoBackup +# Is set if backups could not be fetched. +# @param pClean +# Is set to turn off status messages. +# @param pReplaceConflicts +# Is set to replace conflicting packages with new ones instead of +# leaving them alone. +# @param pExitOnConflict +# Is set to stop the program if a conflict is encountered. +# @param pForce +# Is set to force the update of packages that are not really updated. +# @param pFetchOnly +# Is set to only fetch packages instead of installing/upgrading them. +# @param pInteractive +# TODO: Reserved for future versions (resume/roll-back). +# @param pJobs +# TODO: Reserved for future versions (pkg_libchk tests). +# @param pListDiscarded +# Is set to activate the listing of packages that are ignored because +# they are not set in the INDEX. +# @param pNoActions +# Is set if no actions should be performed but a list of what would have +# been done should get printed. +# @param pNoLogging +# Turn off logging. +# @param pParanoid +# Is set to activate cyclic dependency checks. +# @param pRecursive +# Is set to activate updating of dependencies. +# @param pMoreRecursive +# Is set to activate updating of dependencies of depending packages. +# @param pUpwardRecursive +# Is set to activate updating of depending packages. +# @param pMoreUpwardRecursive +# Is set to activate updating of packages depending on dependencies. +# @param pVerbose +# Is set to activate informative output. +# +readParams() { + local arg package escapedPkg depth + # Store the recursion depth. Note that counting down is dealt with + # by making depth local. + depth=$((${depth:--1} + 1)) + + # This is used to remember whether the next parameter should + # be a replacing package or a packge to be replaced. + origin=${origin:-0} + + for arg { + # + # Handle package replacements. + # + if [ $origin -eq 1 ]; then + # Store the replacement. + package="$(identifyPackage "$arg")" || exit $? + if [ -z "$package" -o "$(($(echo "$package" | wc -l)))" -ne "1" ]; then + error $ERR_ARG "The package identifier <$arg> is not unambiguous." + fi + upgrade="$upgrade${upgrade:+$IFS}$package" + replace="$replace${replace:+$IFS}$package" + origin=2 + # Request the download. + downloadManagerMsgRequest "$package" + continue + fi + if [ $origin -eq 2 ]; then + # Store what to replace. + # This is taken from the package database not the index. + + case "$arg" in + */*) + # Assume arg is an origin. + package="$(pkg_info -qO "$arg")" + package="$(pkg_info -qo "$package" 2> /dev/null);$package" + ;; + *) + # Assume arg is a package identifier. + package="$(pkg_info -qo "$arg" 2> /dev/null);$(pkg_info -E "$arg" 2> /dev/null)" + + # Maybe arg is a package identifier + # without a version tail. + if [ "$package" = ";" ]; then + package="$(pkg_info -qo "$arg-*" 2> /dev/null);$(pkg_info -E "$arg-*" 2> /dev/null)" + fi + ;; + esac + + # Arg is not installed. + if [ "$package" = ";" ]; then + error $ERR_ARG "The package <$arg> is not installed and thus cannot be replaced." + fi + # It appears arg is an identifier that is + # not unambiguous. + if [ "$(($(echo "$package" | wc -l)))" -ne "1" ]; then + error $ERR_ARG "The package identifier <$arg> is not unambiguous." + fi + # A package can only be replaced once. + escapedPkg="$(echo "$package" | getIdxEscape)" + if echo "$replace" | grep -qEx ".*\|$escapedPkg"; then + error $ERR_ARG "The package <$arg> is already listed for replacement." + fi + replace="$replace|$package" + origin=0 + continue + fi + + # + # Identify arguments. + # + case "$arg" in + "-a" | "--all") + pAll=1 + if [ -n "$pRecursive" ]; then + error $ERR_ARG "Recursiveness has no effect, because all packages are already selected for processing." + fi + if [ -n "$pUpwardRecursive" ]; then + error $ERR_ARG "Upward recursiveness has no effect, because all packages are already selected for processing." + fi + ;; + "-b" | "--no-backup") + pNoBackup=1 + ;; + "-c" | "--clean") + pClean=-c + ;; + "-C" | "--replace-conflicts") + if [ -n "$pExitOnConflict" ]; then + error $ERR_ARG "The 'replace conflicts' and 'exit on conflict' modes are mutually exclusive." + fi + pReplaceConflicts=1 + ;; + "-d" | "--list-discarded") + pListDiscarded=1 + ;; + "-f" | "--force") + pForce=1 + ;; + "-F" | "--fetch-only") + if [ -n "$pNoActions" ]; then + error $ERR_ARG "The 'no actions' and 'fetch only' modes are mutually exclusive." + fi + pFetchOnly=1 + ;; + "-h" | "--help") + printHelp + ;; + "-i" | "--interactive") + # TODO: not yet used + pInteractive=1 + ;; + -j* | --jobs*) + # TODO: not yet used + pJobs="$arg" + if ! pkg_libchk "$pJobs" DUMMY/DUMMY 1>&2; then + exit $ERR_ARG + fi + ;; + "-l" | "--no-logging") + pNoLogging=1 + ;; + "-n" | "--no-actions") + if [ -n "$pFetchOnly" ]; then + error $ERR_ARG "The 'no actions' and 'fetch only' modes are mutually exclusive." + fi + pNoActions=1 + ;; + "-o" | "--origin") + # Make sure the local index copy is up to date. + getIndex + origin=1 + ;; + "-p" | "--paranoid") + pParanoid=1 + ;; + "-r" | "--recursive") + if [ -n "$pMoreRecursive" ]; then + error $ERR_ARG "There are only two levels of recursiveness." + elif [ -n "$pRecursive" ]; then + pMoreRecursive=1 + else + pRecursive=1 + fi + if [ -n "$pAll" ]; then + error $ERR_ARG "Recursiveness has no effect, because all packages are already selected for processing." + fi + ;; + "-R" | "--upward-recursive") + if [ -n "$pMoreUpwardRecursive" ]; then + error $ERR_ARG "There are only two levels of upward recursiveness." + elif [ -n "$pUpwardRecursive" ]; then + pMoreUpwardRecursive=1 + else + pUpwardRecursive=1 + fi + if [ -n "$pAll" ]; then + error $ERR_ARG "Upward recursiveness has no effect, because all packages are already selected for processing." + fi + ;; + "-v" | "--verbose") + pVerbose=-v + ;; + "-X" | "--exit-on-conflict") + if [ -n "$pReplaceConflicts" ]; then + error $ERR_ARG "The 'exit on conflict' and 'replace conflicts' modes are mutually exclusive." + fi + pExitOnConflict=1 + ;; + -? | --*) + error $ERR_ARG "Unknown parameter \"$arg\"." + ;; + -*) + # Split parmeters. + readParams "${arg%%${arg#-?}}" "-${arg#-?}" + ;; + *) + # Make sure the local index copy is up to date. + getIndex + # Add package to the list of packages to + # upgrade/install. + package="$(identifyPackage "$arg")" || exit $? + upgrade="$upgrade${upgrade:+$IFS}$package" + # Request the download. + downloadManagerMsgRequest "$package" + ;; + esac + } + + # + # Only perform the following steps if this is the root call + # to this function (recursion depth = 0). + # + if [ $depth -eq 0 ]; then + # + # Deal with missing parameters. + # + if [ $origin -eq 1 ]; then + error $ERR_ARG "Incomplete parameters, missing origin." + fi + if [ $origin -eq 2 ]; then + error $ERR_ARG "Incomplete parameters, missing package to replace." + fi + + # + # Deal with invalid levels of recursiveness. + # + if [ -n "$pMoreRecursive" -a -z "$pUpwardRecursive" ]; then + error $ERR_ARG "Thorough recursiveness can only be used in conjunction with upwards recursiveness." + fi + + # + # Remove duplicates in the list of packages to upgrade. + # + upgrade="$(echo "$upgrade" | sort -u)" + # Reset global variables. + origin= + fi +} + +# +# Reads all the required +CONTENTS information from a package. +# The information is stored in variables. +# +# @param 1 +# The name of the package file. +# @param pkgname +# The name of the package. +# @param origin +# The origin of the package. +# @param depends +# The dependencies of the package in the format "<origin>;<package>", +# in reverse order. +# @param conflicts +# A list of regular expressions that can be used to identify conflicting +# packages. +# +readContents() { + local contents line format + contents="$(tar -xOf "$1" '+CONTENTS')" + format= + + pkgname= + origin= + depends= + conflicts= + for line in $contents; { + case "$line" in + @name\ *) + pkgname="${line#@name }" + ;; + @pkgdep\ *) + depends=";${line#@pkgdep }${depends:+$IFS}$depends" + ;; + @comment\ *) + line="${line#@comment }" + case "$line" in + DEPORIGIN:*) + depends="${line#*:}$depends" + ;; + PKG_FORMAT_REVISION:*) + format="${line#*:}" + ;; + ORIGIN:*) + origin="${line#*:}" + ;; + esac + ;; + @conflicts\ *) + conflicts="${conflicts:+$conflicts$IFS}${line#@conflicts }" + ;; + esac + } + + if [ "$format" != "1.1" ]; then + error $ERR_PACKAGE_FORMAT "Unknown package format in <$1>, bailing out!" + fi + + return 0 +} + +# +# Starts a download manager that can be instructed through a queue. +# A process that wants to know what's going on with the download manager +# can simply read from the queue as well. +# +# The download manager keeps as many downloads running as there are +# PACKAGESITE_MIRRORS. Should a download fail, it is retried as soon +# as no untried downloads remain. Every download is only retried once. +# A download is never attempted from the master server. +# +# @param queueMessages +# The queue to create and read from. +# @param packagerepos +# The location of the remote package repository (derived from +# PACKAGESITE). If this is identical with the local repository, +# the download manager will not be started. +# @param pNoActions +# If set, the download manager will not be started. +# +downloadManager() { + # No actions mode, this includes no downloads. + test -n "$pNoActions" && return 0 + + # Packages are locally available, no downloads. + test "$PACKAGES" = "$packagerepos" && return 0 + + verbose "Start the download manager." + + # Initialize the queue. + rm "$queueMessages" 2> /dev/null + touch "$queueMessages" + + # + # The following block is forked away. + # Note that all variable assignments happen in a separate process + # and hence have no effect on the outside. + # + ( + # Remove the queue when exiting and get rid of pending jobs. + trap " + kill \$(jobs -ls) > /dev/null 2>&1 + rm '$queueMessages' 2> /dev/null + exit + " EXIT sigint sigterm + + # The jobs yet to be done. + jobs= + # The available mirrors. + mirrors="$PACKAGESITE_MIRRORS" + # The jobs that should be retried. + retry= + # The jobs that have been retried. + retried= + # The last line read from the socket. + line= + + # Keep on running as long as the father process is around. + # Note that this while loop has the message queue as stdin. + while kill -0 "$pid" 2> /dev/null; do + # Check for a message in the queue. + # There is nothing to be done, if there was no message, + # none the less it times out to allow the terminal + # to catch signals. + read -t 2 line + # Process messages. + case "$line" in + finished:*) + # A download has been finished. + # Add the mirror that was used to + # the list of available mirrors. + mirror="${line#finished:}" + mirror="${mirror%;*}" + mirrors="${mirrors:+$mirrors$IFS}$mirror" + ;; + retry:*) + # A download was not finished + # successfuly. + mirror="${line#retry:}" + job="${mirror##*;}" + mirror="${mirror%;*}" + if echo "$retried" | grep -qFx "$job"; then + # If this package has already + # had a retry, mark it as + # finished to hand it over + # to the package validation + # that can fetch from the + # master server. + downloadManagerMsgFinished "$mirror" "$job" + else + # The first retry request. + # Free the mirror and list + # the package for retry. + mirrors="${mirrors:+$mirrors$IFS}$mirror" + retry="${retry:+$retry$IFS}$job" + fi + ;; + request:*) + # Append requested downloads to the + # list of available jobs. + jobs="${jobs:+$jobs$IFS}${line#request:}" + ;; + exit) + # The download manager has been told + # to terminate. + break + ;; + esac + # Delete the line, so it cannot be read again in the + # next iteration, if reading from the queue has + # timed out. + line= + + # If any mirrors are available and there are jobs + # in the queue, now is the time to dispatch them. + while [ -n "$jobs" -a -n "$mirrors" ]; do + mirror="${mirrors%%$IFS*}" + mirrors="${mirrors#$mirror}" + mirrors="${mirrors#$IFS}" + job="${jobs%%$IFS*}" + jobs="${jobs#$job}" + jobs="${jobs#$IFS}" + downloadManagerFetch "$mirror" "$job" & + done + + # If we have run out of jobs, give the retry stuff. + # a try. + while [ -n "$retry" -a -n "$mirrors" ]; do + mirror="${mirrors%%$IFS*}" + mirrors="${mirrors#$mirror}" + mirrors="${mirrors#$IFS}" + job="${retry%%$IFS*}" + retry="${retry#$job}" + retry="${retry#$IFS}" + # Remember that this job has been retried. + retried="${retried:+$retried$IFS}$job" + downloadManagerFetch "$mirror" "$job" & + done + done < "$queueMessages" + ) & +} + +# +# This is forked off by the download manager to download a package from +# a mirror. +# If the package is already present a download is not attempted. +# +# @param 1 +# The mirror to download from. +# @param 2 +# The name of the package to download. +# +downloadManagerFetch() { + # Get rid of pending jobs. + trap " + kill \$(jobs -ls) > /dev/null 2>&1 + exit + " EXIT sigint sigterm + + # Only do something if the package is not present. + if ! [ -e "$PACKAGES/All/$2.tbz" ]; then + # Create the download location. + mkdir -p "$PACKAGES/All" 2> /dev/null + + # Attempt download from mirror. + # This is forked off, to allow the shell to catch signals. + fetch -qmo "$PACKAGES/All/$2.tbz" "${1%/*?}/All/$2.tbz" > /dev/null 2>&1 & + if ! wait $!; then + # Release the mirror and mark the package for a retry. + downloadManagerMsgRetry "$1" "$2" + return 0 + fi + fi + + # Release the mirror and mark package finished. + downloadManagerMsgFinished "$1" "$2" +} + +# +# Tells the download manager, that a download was unsuccessful. +# +# @param 1 +# The mirror that was used. +# @param 2 +# The name of the package that was not downloaded. +# @param queueMessages +# The message queue to the download manager. +# +downloadManagerMsgRetry() { + # Do not send anything without a queue. + test ! -e "$queueMessages" && return 0 + + lockf -k "$queueMessages" sh -c "echo 'retry:$1;$2' >> '$queueMessages'" +} + +# +# Tells the download manager, that a download has been finished. +# +# @param 1 +# The mirror that was used. +# @param 2 +# The name of the downloaded package. +# @param queueMessages +# The message queue to the download manager. +# +downloadManagerMsgFinished() { + # Do not send anything without a queue. + test ! -e "$queueMessages" && return 0 + + lockf -k "$queueMessages" sh -c "echo 'finished:$1;$2' >> '$queueMessages'" +} + +# +# Requests the download of packages from the download manager. +# +# @param 1 +# A list of packages for download. +# @param queueMessages +# The message queue to the download manager. +# +downloadManagerMsgRequest() { + # Do not send anything without a queue. + test ! -e "$queueMessages" && return 0 + + local request + for request in $1; { + lockf -k "$queueMessages" sh -c "echo 'request:${request#*;}' >> '$queueMessages'" + } +} + +# +# Instructs the download manager to terminate. +# +# @param queueMessages +# The message queue to the download manager. +# +downloadManagerMsgExit() { + # Do not send anything without a queue. + test ! -e "$queueMessages" && return 0 + + lockf -k "$queueMessages" sh -c "echo 'exit' >> '$queueMessages'" +} + +# +# Validates a single package. Validation means it checks whether a package +# is a complete tar archive. Damaged or missing packages will be (re)downloaded +# from the master server (the one named by PACKAGESITE). +# If the package is a valid tar archive the +CONTENTS file will be checked, +# as well. +# +# @param 1 +# The name of the package to validate. +# @param packagerepos +# The location of the remote package collection. This is derived from +# PACKAGESITE. +# @param pending +# The list of pending packages. +# @return +# Return 0 on success. +# +validatePackage() { + local package + package="$1.tbz" + + # Check whether the package is intact and present. + if ! tar -tf "$PACKAGES/All/$package" > /dev/null 2>&1; then + # If the package repository and the local package collection + # are identical, there's no chance to get the package if it's + # not already there. + if [ "$PACKAGES" = "$packagerepos" ]; then + error $ERR_FETCH "The package <$package> is not present." + fi + + # Clean up whatever crap is there. + rm "$PACKAGES/All/$package" 2> /dev/null + + # Try to get the package from the master server. + fetch -mo "$PACKAGES/All/$package" "$packagerepos/All/$package" + + # Check whether the package is present. + if ! [ -e "$PACKAGES/All/$package" ]; then + error $ERR_FETCH "The package <$package> could not be fetched." + fi + + # Check whether the package is a valid tar archive. + if ! tar -tf "$PACKAGES/All/$package" > /dev/null 2>&1; then + error $ERR_FETCH "The package <$package> could not be read." + fi + fi + + # Check whether we can read the package +CONTENTS format. + readContents "$PACKAGES/All/$package" + + # Remove this package from the list of pending packages. + pending="$(echo "$pending" | grep -vFx "$1")" + + # The package is present and intact. + return 0 +} + +# +# Let's get it on! The declarative part is finally over. +# + +# Ignore some signals that should not occur. +trap 'warn "Discard signal SIGHUP."' sighup +trap 'warn "Discard signal SIGUSR1."' sigusr1 +trap 'warn "Discard signal SIGUSR2."' sigusr2 + +# +# Parse command line parameters. +# +readParams "$@" + +# Make sure the index is available for the following operations. +getIndex + +# +# Populate the list of packages out of sync with the index. +# +pkgAll + +# +# Perform dependency checking. +# +pkgDependencies + +# +# Sort packages by their dependencies. +# +pkgSort + +# +# Display tasks. +# +pkgList + +# +# Download packages. +# +pkgDownload + +# +# Upgrade packages. +# +pkgUpgrade + +exit 0 diff --git a/sysutils/bsdadminscripts/files/uma.in b/sysutils/bsdadminscripts/files/uma.in new file mode 100644 index 000000000000..6bba2b122d8b --- /dev/null +++ b/sysutils/bsdadminscripts/files/uma.in @@ -0,0 +1,436 @@ +#!/bin/sh -f +# +# Copyright (c) 2009 +# Dominic Fandrey <kamikaze@bsdforen.de> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +readonly version=1.1.1 +readonly name=uma + +# Return value. +errno=0 +# Allow things to fail properly by ignoring SIGINT in the main process. +trap '' int + +# Used to activate verbose output. +verbose= +# Will be set if files are locally available. +local= + +vardir="%%VAR%%" +lock="$vardir/run/$name.lock" +lockpid="$vardir/run/$name.pid" +identpid="$vardir/run/$name.ident.pid" +conf="%%PREFIX%%/etc/$name.conf" + +# Use line breaks as a delimiter. +IFS=' +' +# Timezone UTC for age comparisons. +export TZ=UTC + +# The bit position of errors. +readonly ERR_LOCK=0 +readonly ERR_ARG=1 +readonly ERR_FETCH_PORTS=2 +readonly ERR_FETCH_VULNDB=3 +readonly ERR_FETCH_INDEX=4 +readonly ERR_EXTRACT_PORTS=5 +readonly ERR_UPDATE_PORTS=6 + +# +# Get environment variables. +# + +# Load the configuration file if present. +if [ -e "$conf" ]; then + . "$conf" +fi + +# Local index location. +: ${PKG_INDEX="$vardir/db/uma/FTPINDEX"} +: ${FTP_TIMEOUT=60} + +# Logic from src/usr.sbin/pkg_install/add/main.c, plus the possibility to +# override the architecture with ARCH. +: ${PACKAGEROOT="ftp://ftp.freebsd.org"} +: ${ARCH="$(uname -m)"} +branch="$(uname -r | tr '[:upper:]' '[:lower:]')" +number="${branch%%.*}" +branch="${branch##*-}" +case "$branch" in + release) + branch=$number-$branch + ;; + stable|current) + branch=${number%%.*}-$branch + ;; + *) + # Fallback to stable for prerelease and the like. + branch=${number%%.*}-stable + ;; +esac +: ${BRANCH=$branch} +: ${PACKAGESITE="$PACKAGEROOT/pub/FreeBSD/ports/$ARCH/packages-$BRANCH/Latest"} +packagetree="${PACKAGESITE%/*?}" +ftp="${PACKAGESITE#*://}" +ftp="${ftp%%/*}" + +# +# Generate PACKAGESITE_MIRRORS if only PACKAGEROOT_MIRRORS are given. +# Note that PACKAGEROOT_MIRRORS and PACKAGESITE_MIRRORS are supposed to be +# a ";" or line feed separated list. Semicolons will be converted to line +# feeds in any case. +# + +# Set PACKAGEROOT_MIRRORS if not set. +if [ -z "$PACKAGEROOT_MIRRORS" ]; then + PACKAGEROOT_MIRRORS= + for i in $(jot 14); { + PACKAGEROOT_MIRRORS="${PACKAGEROOT_MIRRORS:+$PACKAGEROOT_MIRRORS$IFS}ftp://ftp$i.FreeBSD.org" + } +fi + +# Convert semicolon in PACKAGEROOT_MIRRORS. +PACKAGEROOT_MIRRORS="$(echo "$PACKAGEROOT_MIRRORS" | sed "s/;/\\$IFS/g")" +# Build PACKAGESITE_MIRRORS. +if [ -z "${PACKAGESITE_MIRRORS}" ]; then + PACKAGESITE_MIRRORS= + for MIRROR in $PACKAGEROOT_MIRRORS; { + PACKAGESITE_MIRRORS="${PACKAGESITE_MIRRORS:+$PACKAGESITE_MIRRORS$IFS}$MIRROR/pub/FreeBSD/ports/$ARCH/packages-$BRANCH/Latest" + } +fi +# Convert semicolon in PACKAGESITE_MIRRORS. +PACKAGESITE_MIRRORS="$(echo "$PACKAGESITE_MIRRORS" | sed "s/;/\\$IFS/g")" + +# Remove duplicates. +PACKAGEROOT_MIRRORS="$(echo "$PACKAGEROOT_MIRRORS" | sort -u)" +PACKAGESITE_MIRRORS="$(echo "$PACKAGESITE_MIRRORS" | sort -u)" + +# Determine portsdir +portsdir=$(make -V PORTSDIR -f /usr/share/mk/bsd.port.mk 2> /dev/null) +portsdir="${portsdir:-%%PORTS%%}" + +export ARCH BRANCH PKG_INDEX FTP_TIMEOUT PACKAGEROOT PACKAGESITE +export PACKAGEROOT_MIRRORS PACKAGESITE_MIRRORS + +# +# This function is called by a trap when the script exits in verbose mode. +# It reads errno to construct error messages. +# +# @param errno +# The exit status of the script. +# +verbose() { + if [ $(($errno >> $ERR_LOCK & 1)) -eq 1 ]; then + echo "ERROR($((1 << $ERR_LOCK))): Lock owned by someone else." + fi + if [ $(($errno >> $ERR_ARG & 1)) -eq 1 ]; then + echo "ERROR($((1 << $ERR_ARG))): An unknown parameter was supplied." + fi + if [ $(($errno >> $ERR_FETCH_PORTS & 1)) -eq 1 ]; then + echo "ERROR($((1 << $ERR_FETCH_PORTS))): Fetching the ports tree failed." + fi + if [ $(($errno >> $ERR_FETCH_VULNDB & 1)) -eq 1 ]; then + echo "ERROR($((1 << $ERR_FETCH_VULNDB))): Fetching security database failed." + fi + if [ $(($errno >> $ERR_FETCH_INDEX & 1)) -eq 1 ]; then + echo "ERROR($((1 << $ERR_FETCH_INDEX))): Fetching remote INDEX failed." + fi + if [ $(($errno >> $ERR_EXTRACT_PORTS & 1)) -eq 1 ]; then + echo "ERROR($((1 << $ERR_EXTRACT_PORTS))): Extracting the ports tree failed." + fi + if [ $(($errno >> $ERR_UPDATE_PORTS & 1)) -eq 1 ]; then + echo "ERROR($((1 << $ERR_UPDATE_PORTS))): Updating the ports tree failed." + fi +} + +# +# This function spawns a process that takes over a lock. +# +# @param pid +# The PID of the process that requested the lock. +# @param lock +# The location of the lock file. +# @param lockpid +# The location of the PID file for the lock holding process. +# +secureLock() { + lockf "$lock" sh -c " + trap 'exit 0' term + echo '$pid' > '$lock' + echo \"\$\$\" > '$lockpid' + trap 'rm \"$lockpid\"; exit 0' EXIT + while kill -0 '$pid' 2> /dev/null; do + sleep 2 + done + " 2> /dev/null & +} + +# +# Checks whether the currently requesting process holds the lock. +# +# @param pid +# The PID of the process that requested the lock. +# @param lock +# The location of the lock file. +# @return +# Returns 0 if the lock is held for the requesting process or 1 +# if the lock is missing or owned by another process. +# +hasLock() { + test "$pid" -eq "$(cat "$lock" 2> /dev/null)" 2> /dev/null + return $? +} + +# +# Creates a lock for the requesting process. +# +# @param pid +# The PID of the process that requested the lock. +# @param lock +# The location of the lock file. +# @param lockpid +# The location of the PID file for the lock holding process. +# @param portsdir +# The location of the FreeBSD ports tree. +# @return +# Returns 0 on success, 1 on failure. +# +lock() { + local location + + # The requestor already holds the lock. + hasLock && return 0 + + # The process requesting the lock does not exist. + kill -0 "$pid" 2> /dev/null || return 1 $(errno=1) + + # Follow symlinks + location="$(pwd)" + if cd "$portsdir" && portsdir="$(pwd -P)"; then + # Portsdir exists, so we can test for make activity. This + # does not cover all cases, but it covers a lot. + if fstat "$portsdir" | awk '{print $2}' | grep -q make; then + errno=1 + return 1 + fi + fi + cd "$location" + + # Try acquiring the lock. + lockf -st 0 "$lock" "$0" secure $pid 2> /dev/null || return 1 $(errno=1) + # Wait until the locking process is properly set up. + while ! [ -e "$lockpid" -a -e "$lock" ]; do + sleep 0.1 + done + return 0 +} + +# +# Frees a lock unless it is held for another process than the requestor. +# +# @param lock +# The location of the lock file. +# @param lockpid +# The location of the PID file for the lock holding process. +# @return +# Returns 0 on success, 1 on failure. +# +unlock() { + if hasLock; then + # Free the lock. + kill -TERM "$(cat "$lockpid")" + # Wait for the locking process to clean up. + while [ -e "$lockpid" -o -e "$lock" ]; do + sleep 0.1 + done + return 0 + else + errno=1 + return 1 + fi +} + +# +# Prints the command and available parameters. +# +# @param name +# The name of the script. +# @param version +# The version of the script. +# +printHelp() { + echo "$name v$version +usage: + $name [-hv] [pid] [fetch] [extract] [update] [...] + $name [-hv] [pid] fetch [ports] [audit] [ftpindex] + $name [-hv] [pid] extract [ports] + $name [-hv] [pid] update [ports] + $name [-hv] lock [pid] + $name [-hv] unlock [pid]" +} + +# +# Reads the parameters and creates variables that indicates the presence +# of these parameters. +# +# The last numeric value is treated as the requestor PID. It also deals +# +# @param @ +# All parameters to process. +# @param verbose +# Set to 1 if verbose mode is activated. +# @param cmd_* +# Set by this function to indicate the presence of a parameter. +# +readParams() { + local flag + for flag; { + # A numerical parameter is the PID. + if [ "$flag" -eq "$flag" ] 2> /dev/null; then + pid="${flag}" + continue + fi + + # Activate verbose mode for -v. + case "$flag" in + -v | --verbose) + trap 'verbose 1>&2' EXIT + verbose=1 + continue + ;; + -h | --help) + printHelp + continue + ;; + -? | --*) + errno=$((1 << $ERR_ARG)) + exit $errno + ;; + -*) + # Split parameters. + readParams "${flag%${flag#-?}}" "-${flag#-?}" + continue + ;; + esac + + # If the variable is not predefined, the command is unknown. + if eval "test -n \"\${cmd_$flag=1}\""; then + errno=$((1 << $ERR_ARG)) + exit $errno + fi + setvar "cmd_$flag" 1 + } +} + +pid="$$" +cmd_lock= +cmd_unlock= +cmd_secure= +cmd_env= +cmd_fetch= +cmd_extract= +cmd_update= +cmd_ports= +cmd_audit= +cmd_ftpindex= +readParams "$@" + +# +# Exclusive commands that will cause all others to be ignored, in order +# of priority. +# + +if [ -n "$cmd_unlock" ]; then + unlock + return $? +fi + +if [ -n "$cmd_secure" ]; then + secureLock + return $? +fi + +if [ -n "$cmd_lock" ]; then + lock + return $? +fi + +# +# Non-exclusive commands that do not require a lock. +# + +if [ -n "$cmd_env" ]; then + echo "ARCH='$ARCH'" + echo "BRANCH='$BRANCH'" + echo "FTP_TIMEOUT='$FTP_TIMEOUT'" + echo "PACKAGEROOT='$PACKAGEROOT'" + echo "PACKAGESITE='$PACKAGESITE'" + echo "PKG_INDEX='$PKG_INDEX'" + echo "PACKAGEROOT_MIRRORS='$PACKAGEROOT_MIRRORS'" + echo "PACKAGESITE_MIRRORS='$PACKAGESITE_MIRRORS'" +fi + +# Create a local lock if need be. +localLock= +if ! hasLock; then + localLock=1 + lock || return $? +fi + +# Ports tree commands. +if [ -n "$cmd_ports" ]; then + if [ -n "$cmd_fetch" ]; then + portsnap fetch || errno="$((1 << $ERR_FETCH_PORTS | $errno))" + fi + if [ -n "$cmd_extract" ]; then + portsnap extract || errno=$((1 << $ERR_EXTRACT_PORTS | $errno)) + fi + if [ -n "$cmd_update" ]; then + portsnap update || errno=$((1 << $ERR_UPDATE_PORTS | $errno)) + fi +fi + +# Portaudit commands. +if [ -n "$cmd_audit" ]; then + if [ -n "$cmd_fetch" ]; then + portaudit -F || errno=$((1 << $ERR_FETCH_VULNDB | $errno)) + fi +fi + +# Package index commands. +if [ -n "$cmd_ftpindex" ]; then + if ! mkdir -p "${PKG_INDEX%/*}" 2> /dev/null; then + test -n "$verbose" \ + && echo "The directory ${PKG_INDEX%/*} does not exist and cannot be created!" + errno=$((1 << $ERR_FETCH_INDEX | $errno)) + elif [ -n "$cmd_fetch" ]; then + fetch -mo "$PKG_INDEX" "$packagetree/INDEX" \ + || errno=$((1 << $ERR_FETCH_INDEX | $errno)) + fi +fi + + +# Free a local lock. +test -n "$localLock" && unlock + +return $errno + |