diff options
Diffstat (limited to 'slackupgrade')
-rw-r--r-- | slackupgrade | 288 |
1 files changed, 238 insertions, 50 deletions
diff --git a/slackupgrade b/slackupgrade index 662b325..ff4d46f 100644 --- a/slackupgrade +++ b/slackupgrade @@ -1,12 +1,12 @@ #!/bin/bash # slackupgrade - full upgrade of a Slackware installation -# Copyright (C) 2019 Sergey Poznyakoff. +# Copyright (C) 2019-2020 Sergey Poznyakoff. # # Slackware-upgrade-system is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License as published +# modify it under the terms of the GNU General Public License as published # by the Free Software Foundation; either version 3, or (at your option) # any later version. # # Slackware-upgrade-system is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the @@ -17,12 +17,13 @@ # <http://www.gnu.org/licenses/>. set -e # Configuration directory : ${SLACKUPGRADE_CONFDIR:=/etc/slackupgrade} +: ${SLACKUPGRADE_PKGDIR:=/var/slackupgrade} # Slackware root directory rooturl= # Log file name logfile= # 'y', if dry-run mode is requested dry_run= @@ -38,16 +39,31 @@ install_series= # Names of additional packages. install_packages= # Name of the keep-list file. keep_file= # Max. count of backup files to keep max_backup=4 +# Display progress bar when downloading +progressbar=1 +# Operating mode. Possible values: +# INCR Incremental mode. Archive for each package is downloaded prior +# to its nstallation and removed immediately afterwards. This +# ensures minimal disk space requirements. +# SAFE Safe mode. All archives are first downloaded to +# SLACKUPGRADE_PKGDIR and then installed. Each archive is removed +# immediately after installing from it. This requires some 2.5G +# of disk space at the beginning, which will be freed by the end +# of upgrade. +# AUTO Select incremental mode if there is enough disk space. If not, +# ask the user if it is OK to proceed in incremental mode. +opmode=AUTO # Internal variables remote= tempdir=${TMP:-/tmp}/slackupg.$$ +strip_series=0 installed_list=$tempdir/installed.list avail_index=$tempdir/avail.index avail_list=$tempdir/avail.list series_names="a ap d @@ -129,129 +145,157 @@ function error() { echo >&2 "$0: $*" if [ -n "$logfile" ]; then echo >>$logfile "$0: $(date): $*" fi } +# Abnormal termination: print error message, remove temporary directory +# and terminate with status 1. function abend() { error "$@" tempdir_remove exit 1 } function package_file_name() { pkg=$(awk -vname=$1 '$1==name { print $2 }' $avail_index) if [ -z "$pkg" ]; then error "package $name not found in index (should not happen!)" else - echo $pkg - fi -} - -function package_name_md5sum() { - pkg=$(awk -vname=$1 '$1==name { print $3 }' $avail_index) + echo $pkg + fi } +# check_package_md5sum PKG ARCHIVE +# Verifies the MD5 sum of the ARCHIVE file for Slackware package PKG function check_package_md5sum() { if [ -n "$checksums" ]; then awk -vcname="$1" -vdname="$2" \ '$2==cname { print $1 " " dname }' $checksums | \ md5sum --status --check fi } +# all_package_names +# Lists all packages from the Slackware distribution. function all_package_names() { grep -v '.*/kde[^/]*/' $avail_index | cut -d ' ' -f 1 } +# series_package_names S +# Lists the names of packages in Slackware series S. function series_package_names() { grep '.*/'"$1/" $avail_index | cut -d ' ' -f 1 } function catfile() { echo $rooturl/${1#./} } +# download_curl FILE URL +# Downloads file from URL to FILE using curl. function download_curl { curl $CURL_OPTIONS -L -sS -o$1 $2 } +# download_wget LOCAL URL +# Downloads file from URL to FILE using wget. function download_wget { if ! wget $WGET_OPTIONS -nv -o wget.log -O$1 $2; then grep -i "failed\|error" wget.log /bin/false fi } +# dnfunc_init +# Initializes the downloader function to wget or curl. function dnfunc_init() { if [ -z "$dnfunc" ]; then if wget --version >/dev/null 2>&1; then dnfunc=download_wget elif curl --version >/dev/null 2>&1; then dnfunc=download_curl else abend "neither curl nor wget is installed" fi fi } +# download FILE +# Downloads package FILE from the remote server. On success, returns the +# full pathname of the downloaded copy. function download() { - local name=$(basename $1) + local name=$SLACKUPGRADE_PKGDIR/$(basename $1) local url=$(catfile $1) if $dnfunc $name $url; then - echo $name + echo $name fi } +# getfile PKG [GPG] +# Retrieves the package archive file for the package PKG. If second argument +# is non-empty, verifies the GPG signature of the file. +# If checksums file is available, verifies also the MD5 checksum of the file. +# +# Returns full pathname of the retrieved file. function getfile() { local name=$(if [ -n "$remote" ]; then - download $1 - else - catfile $1 - fi) - + download $1 + elif [ $strip_series -eq 1 ]; then + catfile $(basename $1) + else + catfile $1 + fi) + if [ -n "$2" ]; then ascname=$(if [ -n "$remote" ]; then - download $1.asc - else - catfile $1.asc - fi) + download $1.asc + else + catfile $1.asc + fi) if [ -n "$ascname" ] \ && ${GPG:-gpg} --verify $ascname $name 2>/dev/null; then - : - else + : + else error "gpg verification failed for $name" return - fi + fi fi if [ -n "$checksums" ] && ! check_package_md5sum $1 $name; then error "ERROR: $1: checksum failed" name= fi echo $name } +# dropfile FILE +# Removes FILE, if it has been downloaded from the remote repository, function dropfile() { - if [ -n "$remote" ]; then + if [ -n "$remote" ] || [ $strip_series -eq 1 ]; then rm $1 fi -} +} +# upgrade_package [OPTIONS] FILE +# Runs upgradepkg with the supplied arguments and captures its output to +# the log file. In verbose mode, filters parts of it to the stdout. function upgrade_local() { if [ -n "$dry_run" ]; then echo "upgradepkg $@" elif [ "$verbosity" = 'v' ]; then upgradepkg "$@" | \ tee -a $logfile | \ sed -n -e '/^| Upgrading/s/^| //p' else upgradepkg "$@" >> $logfile fi } +# upgrade_package NAME [OPTIONS] +# Upgrades the package NAME. OPTIONS will be passed to upgradepkg verbatim. function upgrade_package() { local name=$1 shift if [ -n "$dry_run" ]; then echo "upgradepkg $@ $name" else @@ -260,12 +304,14 @@ function upgrade_package() { upgrade_local $@ $file dropfile $file fi fi } +# version_gt A B +# Returns true if version A is greater than B. function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1" } # backup FILE [DRY_RUN] # Create a backup copy of FILE, named FILE~ @@ -287,31 +333,85 @@ function backup() { if [ -r "$1~" ]; then find $dir -name "$stem*" -printf '%f\n' |\ sed -n -r -e 's/(.+)~([[:digit:]]+)$/\2 \1/p' |\ sort +0 -1 -n -r |\ while read n stem do - if [ -n "$max_backup" ] && [ $n -ge $max_backup ]; then - $p rm $dir/${stem}~$n - else - $p mv $dir/${stem}~$n $dir/${stem}~$((n + 1)) - fi - done + if [ -n "$max_backup" ] && [ $n -ge $max_backup ]; then + $p rm $dir/${stem}~$n + else + $p mv $dir/${stem}~$n $dir/${stem}~$((n + 1)) + fi + done $p mv "$1~" "$1~1" - fi + fi $p mv "$1" "$1~" fi } +# disk_avail_size DIR +# Returns the available disk size (in kilobytes) on the device where +# DIR is located +function disk_avail_size() { + df -k --output=target,avail | \ + sed 1d | \ + awk -vdir=$1 \ + 'BEGIN { dirlen=length(dir) } + { len = length($1) + if (len <= dirlen && substr(dir, 1, len) == $1) { + if (mp_len < len) { + mp_len = len + mp_avail = $2 + } + } + } + END { print mp_avail }' +} + +# Downloads all selected packages to the spool directory. +function download_all() { + info "downloading packages" + if [ ${COLUMNS:-0} -lt 10 ]; then + progressbar=0 + fi + if [ $progressbar -eq 1 ]; then + total=$(wc -l candidates | cut -d ' ' -f 1) + width=$(( $COLUMNS - 7 )) + fi + i=0 + cat candidates | + while read pkg + do + if [ $progressbar -eq 1 ]; then + n=$(( $i * $width / $total )) + s=$(printf "%${n}s" ' '|sed 's/./=/g') + printf "\r% 3d%% %s>" $(( $i * 100 / $total)) $s + fi + if [ -z "$dry_run" ]; then + file=$(getfile $(package_file_name $pkg)) + if [ -z "$file" ]; then + abend "failed to download $pkg" + fi + fi + i=$(( $i + 1 )) + done || exit $? + if [ $progressbar -eq 1 ]; then + s=$(printf "%${width}s" ' '|sed 's/./=/g') + printf "\r% 3d%% %s|\n" 100 $s + fi +} + # ########## # Main # ########## -while getopts "ahknp:qs:vy" OPTION +while getopts "ahIknp:qSs:vy" OPTION do case $OPTION in + I) opmode=INCR;; + S) opmode=SAFE;; a) install_all=y;; h) usage exit 0;; k) keep_file=$OPTARG;; n) dry_run=y;; p) install_packages="$install_packages $OPTARG";; @@ -351,12 +451,16 @@ fi case "$(uname -m)" in i?86) ARCH=;; x86_64) ARCH=64;; *) abend "architecture $(uname -m) is not yet supported" esac +if [ ! -d $SLACKUPGRADE_PKGDIR ]; then + mkdir -p $SLACKUPGRADE_PKGDIR || abend "can't create $SLACKUPGRADE_PKGDIR" +fi + tempdir_create cd $tempdir mirrors_url=https://mirrors.slackware.com/slackware if [ -z "$rooturl" ]; then dnfunc_init @@ -373,13 +477,13 @@ if [ -z "$rooturl" ]; then if [ -n "$new_version" ]; then rooturl="$mirrors_url/slackware$ARCH-$new_version" info "using $rooturl as distribution top-level URL" else abend "can't find distribution newer than $VERSION; please supply URL if you have any" fi -fi +fi # Check if rooturl is local or remote case $rooturl in /*) unset remote ;; http://*|https://*|ftp://*|ftps://*) @@ -391,12 +495,14 @@ esac if [ -z "$remote" ]; then # Check if rooturl exists and contains the necessary files and directories if [ ! -d $rooturl ]; then abend "$rooturl does not exist" fi + # Safe mode is meaningless with local repository + opmode=INCR fi # Check if rooturl contains all we need info "verifying distribution" # @@ -406,20 +512,20 @@ checksums=$(getfile CHECKSUMS.md5 gpg) if [ -z "$checksums" ]; then abend "CHECKSUMS.md5 not found in $rooturl" fi announce=$(tail +13 $checksums | \ sed -n -r\ - -e 's/^[0-9a-fA-F]+[[:space:]]+(\.\/ANNOUNCE\.[[:digit:]_]+)$/\1/p') + -e 's/^[0-9a-fA-F]+[[:space:]]+(\.\/ANNOUNCE\.[[:digit:]_]+)$/\1/p') if [ -z "$announce" ]; then abend "ANNOUNCE not found in $rooturl" -fi +fi file=$(getfile $announce) if [ -z "$file" ]; then abend "file $announce not found in $rooturl" -fi +fi newversion=$(echo "$announce" | sed -e 's/\.\/ANNOUNCE\.//' -e 's/_/./g') if [ -z "$newversion" ]; then abend "cannot determine new version" fi @@ -448,25 +554,25 @@ do fi file=$(getfile $n) if [ -z "$file" ]; then abend "exiting" fi dropfile $file -done +done # Build a list of installed packages ls /var/log/packages |\ sed -r -e 's/-[^-]+-(i386|x86(_64)?|arm|noarch|fw)-[[:digit:]]+(_.*)?//' |\ sort > $installed_list # Build a list of packages to install (if [ -n "$install_all" ]; then all_package_names -else +else comm -1 -2 $installed_list $avail_list -fi +fi for s in $install_series do series_package_names $s done if [ -n "$install_packages" ]; then echo $install_packages @@ -474,39 +580,39 @@ fi) | sort -u > candidates.$$ # Build a list of packages that should be removed after install comm -2 -3 $installed_list candidates.$$ > remove.list.$$ if [ -s "$keep_file" ]; then grep -v '^#' $keep_file | \ - tr -s '\n' | sort | comm -2 -3 remove.list.$$ - > remove.list + tr -s '\n' | sort -u | comm -2 -3 remove.list.$$ - > remove.list rm remove.list.$$ else mv remove.list.$$ remove.list fi # Create $remove_report and the list of installation candidates. if [ -f "$SLACKUPGRADE_CONFDIR/$VERSION-$newversion.repl" ]; then info "reading $SLACKUPGRADE_CONFDIR/$VERSION-$newversion.repl" # Update candidates and save the replacement map in a temporary. awk '{ sub(/#.*$/,""); sub(/[[:space:]]+$/,"") } - /\\$/ { sub(/\\$/,""); p = p $0; next } - NF == 0 { if (p) print p; p = ""; next } - { print p $0; p="" }' \ + /\\$/ { sub(/\\$/,""); p = p $0; next } + NF == 0 { if (p) print p; p = ""; next } + { print p $0; p="" }' \ $SLACKUPGRADE_CONFDIR/$VERSION-$newversion.repl | \ - sort +0 -1 | \ + sort +0 -1 | \ tee rename.$$ | \ join - remove.list | \ cut -d ' ' -f 2- | \ tr ' ' '\n' | \ cat - candidates.$$ | \ sort -u > candidates # Use the temporary to remove the original package names from the report. awk '{print $1}' rename.$$ | \ - comm -13 - remove.list > $remove_report + comm -13 - remove.list > $remove_report # Clean up - rm rename.$$ + rm rename.$$ else cp remove.list $remove_report mv candidates.$$ candidates fi # Disable interrupts during critical section @@ -516,13 +622,13 @@ if [ -s $remove_report -a "$verbosity" != 'q' ]; then (cat <<EOF INFO: The following packages will be removed. You will have to manually restore them after the upgrade. The package names are preserved for you in the file $remove_report: EOF -cat $remove_report) | ${PAGER:-more} + cat $remove_report) | ${PAGER:-more} fi if ! version_gt $newversion $VERSION; then if ! getyn n "Distribution version $newversion is same or lower than current version $VERSION. Downgrade"; then tempdir_remove error "upgrade canceled" @@ -533,21 +639,104 @@ fi if ! getyn n "Ready for upgrade from $VERSION to $newversion. Continue"; then tempdir_remove error "upgrade canceled" exit 0 fi +if [ $opmode != INCR ]; then + # Automatic mode selection. Compute total compressed size of packages + # to be installed and compare it with available disk space on the device + # hosting SLACKUPGRADE_PKGDIR. Select safe mode if there is enough space, + # otherwise ask if the user wishes to continue in incremental mode. + packages=$(download PACKAGES.TXT) + if [ -z "$packages" ]; then + abend "PACKAGES.TXT not found in $rooturl" + fi + download_size=$(sed -n -r \ + -e '/PACKAGE NAME:[[:space:]]*/{' \ + -e 's///' \ + -e 's/^(.*)-[^-]+-(i386|x86(_64)?|arm|noarch|fw)-[[:digit:]]+(_.*)?\.t.z$/\1 &/' \ + -e h \ + -e '}' \ + -e '/PACKAGE SIZE \(compressed\):[[:space:]]*/{' \ + -e 's///' \ + -e H \ + -e x \ + -e 's/\n/ /' \ + -e p \ + -e '}' \ + $packages | \ + sort +0 -1 | \ + join candidates - | \ + awk '{ if ($4 == "M") s += $3*1024 + else if ($4 == "G") s += $3*1024*1024 + else s += $3 } + END { print s }') + avail_size=$(disk_avail_size $SLACKUPGRADE_PKGDIR) + if [ $avail_size -lt $download_size ]; then + error "not enough free space in $SLACKUPGRADE_PKGDIR" + error "needed ${download_size}K, but only ${avail_size}K available" + if [ $opmode = INCR ]; then + abend "terminating" + fi + + cat >&2 <<EOT + +Safe mode is the default for slackupgrade. In this mode, the program would +first download all packages from the remote site, verify them and then +proceed to actual upgrade. This ensures that even if something goes wrong +during the upgrade, you have all tarballs at hand and can resume from +where you left after fixing the problem. + +This mode cannot be used due to the disk space shortage. Slackupgrade can +nevertheless continue in incremental download mode. In this mode, it will +download and install packages one by one. This takes more time and is less +error prone. If you choose this mode, type 'Y' at the prompt below. Other- +wise, type 'N' to terminate the program, and follow the instructions you +will get. + +EOT + + if getyn n "Continue in incremental download mode"; then + opmode=INCR + else + cat >&2 <<EOT + +Please make sure the slackupgrade spool directory can accomodate at least +${download_size}K of data. To do so, either free sufficient disk space on +the device where $SLACKUPGRADE_PKGDIR resides, or set the SLACKUPGRADE_PKGDIR +environment variable to the full pathname of the new spool directory, + +EOT + error "Terminating." + exit 0 + fi + else + # Download all packages + download_all + # Switch to the local installation mode + rooturl=$SLACKUPGRADE_PKGDIR + opmode=INCR + # Instruct getfile to strip off the series directory from the + # package names. + strip_series=1 + # Disable remote indicator and checksum verification (all archives + # have already been verified during the download). + unset remote checksums + fi +fi + info "starting upgrade of Slackware $VERSION to $newversion${dry_run:+ (DRY RUN MODE)}" pkg_names="glibc-solibs pkgtools tar xz findutils" if [ -n "$remote" ]; then # If using the remote repository, add wget and the libraries it # depends on. Wget is preferred over curl because of its limited # number of dependencies. pkg_names="$pkg_names aaa_elflibs wget" -fi +fi pkg_re='^('$(echo "$pkg_names" | sed -r -e 's/ +/|/g')')$' pkg_files= info "downloading essential packages" for pkg in $pkg_names do @@ -562,13 +751,13 @@ info "installing essential packages" upgrade_local $pkg_files if [ -n "$remote" ]; then # Until now we have been using whatever downloader that is available # (curl or wget). Switch to wget: it has all its dependecies installed. dnfunc=download_wget -fi +fi # Upgrade the rest of packages egrep -v "$pkg_re" candidates |\ while read name do package_file_name $name @@ -627,7 +816,6 @@ EOF *Before* attempting to reboot your system, please make sure that the bootloader has been updated for the new kernel! Good luck! EOF fi - |