#!/bin/bash pkgbase() { # basename + strip extension .tlz echo "$1" | sed 's?.*/??;s/\.tlz$//' } exitstatus=0 # So that we know what to expect... umask 022 usage() { cat << EOF Usage: installpkg [options] Installpkg is used to install a .tlz package like this: installpkg slackware-package-1.0.0-i486-1.tlz options: --warn (warn if files will be overwritten, but do not install) --root /mnt (install someplace else, like /mnt) --threads For plzip compressed packages, set the max number of threads to be used for decompression. Only has an effect if a multithreaded compressor was used, and then only on large packages. For plzip, the default is equal to the number of CPU threads available on the machine. --no-overwrite When extracting the package, do not overwrite existing files. Usually, this option should not be used. It exists so that upgradepkg can use it for the second installation pass. The first pass has already overwritten the previous package's files, and this will catch the few corner cases without generating unnecessary writes. EOF } # Eliminate whitespace function: crunch() { while read FOO ; do echo $FOO done } # Strip version, architecture and build from the end of the name package_name() { pkgbase $1 | sed 's?-[^-]*-[^-]*-[^-]*$??' } # Set maximum number of threads to use. By default, this will be the number # of CPU threads: cputhreads="$(nproc)" # Parse options: while [ 0 ]; do if [ "$1" = "--no-overwrite" ]; then NO_OVERWRITE=" --skip-old-files " shift 1 elif [ "$1" = "-threads" -o "$1" = "--threads" ]; then cputhreads="$2" shift 2 elif [ "$1" = "-root" -o "$1" = "--root" ]; then if [ "$2" = "" ]; then usage exit fi rootdir="$2" shift 2 else break fi done # usage(), exit if called with no arguments: if [ $# = 0 ]; then usage; exit fi # If we have mcookie and a tar that is recent enough to support --transform, # then we can stop needlessly erasing files in the /install directory while # also making installpkg thread-safe. Don't check for recent tar - we'll # already break from --attrs and --xattrs anyway if the wrong tar is used. if which mcookie 1> /dev/null 2> /dev/null ; then mgcookie=$(mcookie) instdir=installpkg-${mgcookie} else # Well, we will make due with this: mgcookie=$$ instdir=installpkg-${mgcookie} fi # Create a lockfile directory if it doesn't exist. We can use it to prevent # screen corruption (from multiple dialogs) and install script collisions # (from multiple scripts trying to work on the same files) in the case of # parallel instances of installpkg. instlockdir=${instlockdir:-/run/lock/pkgtools} if [ ! -d $instlockdir ]; then mkdir -p $instlockdir fi # Set the prefix for the package database directories (packages, scripts). adm_dir="$rootdir/var/lib/pkgtools" # Set the prefix for the removed packages/scripts log files: log_dir="$rootdir/var/log/pkgtools" # If the directories don't exist, "initialize" the package database: for pkgdbdir in douninst.sh packages scripts setup ; do if [ ! -d $adm_dir/$pkgdbdir ]; then mkdir -p $adm_dir/$pkgdbdir chmod 755 $adm_dir/$pkgdbdir fi done for pkglogdir in removed_packages removed_scripts ; do if [ ! -d $log_dir/$pkglogdir ]; then rm -rf $log_dir/$pkglogdir # make sure it's not a symlink or something stupid mkdir -p $log_dir/$pkglogdir chmod 755 $log_dir/$pkglogdir fi done # Likewise, make sure that the symlinks in /var/log exist. We no longer # trust anything to remain in /var/log. Let the admin wipe it if that's # what they like. for symlink in packages scripts setup ; do if [ ! -L $log_dir/../$symlink -a ! -d $log_dir/../$symlink ]; then ( cd $log_dir/.. ; ln -sf ../lib/pkgtools/$symlink . ) fi done # Make sure there's a proper temp directory: tmp=$adm_dir/setup/tmp # If the $tmp directory doesn't exist, create it: if [ ! -d $tmp ]; then mkdir -p $tmp chmod 700 $tmp # no need to leave it open fi # Main loop: for package in $* ; do # Simple package integrity check: if [[ ! -f $package ]]; then echo "Cannot install $package: file not found" continue; fi # "shortname" isn't really THAT short... # it's just the full name without ".tlz" shortname="$(pkgbase $package)" packagedir="$(dirname $package)" # This is the base package name, used for grepping tagfiles and descriptions: packagebase="$(package_name $shortname)" # Reject package if it does not end in '.tlz': if [ "$shortname" = "$(basename $package)" ]; then exitstatus=3 echo "Cannot install $package: file does not end in .tlz" continue; fi # Determine extension: packageext="$(echo $package | rev | cut -f 1 -d . | rev)" # Enforce lzip compliance if [[ $packageext = tlz ]] ; then if which plzip 1> /dev/null 2> /dev/null ; then packagecompression="plzip --threads=${cputhreads}" elif which lzip 1> /dev/null 2> /dev/null ; then packagecompression=lzip else echo "ERROR: lzip compression utility not found in \$PATH." exit 3 fi else echo "ERROR: file is not a tlz (lzip) file" exit 3 fi # Test presence of external compression utility: if ! $(echo $packagecompression | cut -f 1 -d ' ') --help 1> /dev/null 2> /dev/null ; then exitstatus=5 echo "Cannot install $package: external compression utility $packagecompression missing" continue; fi # Figure out some package information, like the compressed and uncompressed # sizes, and where to find the package description: COMPRESSED="$(/bin/du -sh "$(readlink -f $package)" | cut -f 1)" # Test tarball integrity and get uncompressed package size: echo echo "Verifying package $(basename $package)." cat $package | $packagecompression -dc | LC_ALL=C dd 2> $tmp/tmpsize${mgcookie} | tar tf - 2> /dev/null 1> $tmp/tmplist${mgcookie} tarstatus=$? if [[ ! $tarstatus = 0 ]]; then exitstatus=1 # tar file corrupt echo "Unable to install $package: tar archive is corrupt (tar returned error code $tarstatus)" rm -f $tmp/tmplist${mgcookie} $tmp/tmpsize${mgcookie} continue fi UNCOMPRESSED="$(cat $tmp/tmpsize${mgcookie} | tail -n 1 | cut -f 1 -d ' ' | numfmt --to=iec)" rm -f $tmp/tmpsize${mgcookie} # If we still don't have a package description, look inside the package. # This requires a costly untar. if [ "$DESCRIPTION" = "" ]; then mkdir -p $tmp/scan${mgcookie} ( cd $tmp/scan${mgcookie} ; $packagecompression -dc | tar xf - install ) < $package 2> /dev/null ( cd $tmp/scan${mgcookie} ; $packagecompression -dc | tar xf - usr/share ) < $package 2> /dev/null if [ "$( find $tmp/scan${mgcookie}/usr/share -name "smbuild" | wc -l)" == "1" ] ; then source "$( find $tmp/scan${mgcookie}/usr/share/doc -name "smbuild" )" # The build file contains the $DESC variable. We use that as our description DESCRIPTION="$desc" fi fi echo "Size: Compressed: ${COMPRESSED}, uncompressed: ${UNCOMPRESSED}." >> $tmp/tmpmsg${mgcookie} rm -f $tmp/controlns${mgcookie} # Emit information to the console: echo "Installing package $(basename $package):" echo "Description: $DESCRIPTION" echo # We don't want same description if multiple packages are to be installed. unset DESCRIPTION # Make sure there are no symbolic links sitting in the way of # incoming package files: grep -v "/$" $tmp/tmplist${mgcookie} | while read file ; do if [ -L "$rootdir/$file" ]; then rm -f "$rootdir/$file" fi done rm -f $tmp/tmplist${mgcookie} # Write the package file database entry and install the package: echo "PACKAGE NAME: $shortname" > $adm_dir/packages/$shortname echo "COMPRESSED PACKAGE SIZE: $COMPRESSED" >> $adm_dir/packages/$shortname echo "UNCOMPRESSED PACKAGE SIZE: $UNCOMPRESSED" >> $adm_dir/packages/$shortname echo "PACKAGE LOCATION: $package" >> $adm_dir/packages/$shortname echo "FILE LIST:" >> $adm_dir/packages/$shortname if [ "$instdir" = "install" ]; then ( cd $rootdir/ ; $packagecompression -dc | tar --acls --xattrs --xattrs-include='*' --keep-directory-symlink $NO_OVERWRITE -xpvf - | LC_ALL=C sort ) < $package >> $tmp/$shortname 2> /dev/null else ( cd $rootdir/ ; $packagecompression -dc | tar --transform "s,^install$,$instdir," --transform "s,^install/,$instdir/," --acls --xattrs --xattrs-include='*' --keep-directory-symlink $NO_OVERWRITE -xpvf - | LC_ALL=C sort ) < $package >> $tmp/$shortname 2> /dev/null fi if [ "$( grep '^\./' $tmp/$shortname | wc -l | tr -d ' ')" = "1" ]; then # Good. We have a package that meets the Slackware spec. cat $tmp/$shortname >> $adm_dir/packages/$shortname else # Some dumb bunny built a package with something other than makepkg. Bad! # Oh well. Bound to happen. Par for the course. Fix it and move on... # We'll assume it's just a recent tar with an unfiltered filelist with all # files prefixed with "./". No guarantees, but this will usually work. cat $tmp/$shortname | sed '2,$s,^\./,,' >> $adm_dir/packages/$shortname fi rm -f $tmp/$shortname # If we see ALWAYS_RUN_INSTALL_SCRIPT in the install script, then we'll # unset PRE_INSTALL_PASS to ensure that the install script will be run even # in a pre-install pass. We haven't found any case yet where skipping the # install script in the pre-install pass breaks a package upgrade, but we'll # add the ability to not skip it so there's a workaround if any corner cases # emerge. Sorry to violate YAGNI like this. ;-) if [ -f $rootdir/$instdir/doinst.sh ]; then if grep -q ALWAYS_RUN_INSTALL_SCRIPT $rootdir/$instdir/doinst.sh ; then unset PRE_INSTALL_PASS fi fi # Run the install script if one exists and this isn't a pre-install pass: if [ -f $rootdir/$instdir/doinst.sh -a ! "$PRE_INSTALL_PASS" = "true" ]; then echo "Executing install script for $(basename $package)." # Don't use locking if the script contains "NOLOCK": if grep -q NOLOCK $rootdir/$instdir/doinst.sh ; then # If bash is available, use sed to convert the install script to use pushd/popd # rather than spawning subshells which is slow on ARM. This will also speed up # install script processing on any platform. if [ -x /bin/bash ]; then ( cd $rootdir/ ; sed -e's?^( cd \([^;]*\);\(.*\) )$?pushd \1 \&\> /dev/null ; \2 ; popd \&\> /dev/null?g ' $instdir/doinst.sh | /bin/bash ) else ( cd $rootdir/ ; sh $instdir/doinst.sh ) fi else # use locking # If bash is available, use sed to convert the install script to use pushd/popd # rather than spawning subshells which is slow on ARM. This will also speed up # install script processing on any platform. if [ -x /bin/bash ]; then ( flock 9 || exit 11 cd $rootdir/ ; sed -e's?^( cd \([^;]*\);\(.*\) )$?pushd \1 \&\> /dev/null ; \2 ; popd \&\> /dev/null?g ' $instdir/doinst.sh | /bin/bash ) 9> $instlockdir/doinst.sh.lock else ( flock 9 || exit 11 cd $rootdir/ ; sh $instdir/doinst.sh ) 9> $instlockdir/doinst.sh.lock fi fi fi # Clean up the mess... if [ -d $rootdir/$instdir ]; then if [ -r $rootdir/$instdir/doinst.sh ]; then cp $rootdir/$instdir/doinst.sh $adm_dir/scripts/$shortname chmod 755 $adm_dir/scripts/$shortname fi if [ -r $rootdir/$instdir/douninst.sh ]; then cp $rootdir/$instdir/douninst.sh $adm_dir/douninst.sh/$shortname chmod 755 $adm_dir/douninst.sh/$shortname echo "$(echo $adm_dir | rev | cut -f 1-3 -d / | rev)/douninst.sh/$shortname" >> $adm_dir/packages/$shortname fi # /install/do*inst.sh and /install/slack-* are reserved locations for the package system. # Heh, not any more with a recent tar :-) ( cd $rootdir/$instdir ; rm -f do*inst.sh slack-* 1> /dev/null 2>&1 ) rmdir $rootdir/$instdir 1> /dev/null 2>&1 fi # If we used a scan directory, get rid of it: if [ -d "$tmp/scan${mgcookie}" ]; then rm -rf "$tmp/scan${mgcookie}" fi rm -f $tmp/tmpmsg${mgcookie} $tmp/reply${mgcookie} echo "Package $(basename $package) installed." done exit $exitstatus