Lane East 的 blog

一百年很短,一秒钟很长

slackware 包管理(pkgtools)分析 upgradepkg

2011-02-04 00:19

#!/bin/sh

# 函数: 显示帮助
usage() {
 cat << EOF

Usage: upgradepkg newpackage [newpackage2 ... ]
       upgradepkg oldpackage%newpackage [oldpackage2%newpackage2 ... ]

Upgradepkg upgrades a Slackware .tgz package from an older version to a
newer one.  It does this by INSTALLING the new package onto the system, and
then REMOVING any files from the old package that aren't in the new package.
If the old and new packages have the same name, a single argument is all that
is required.  If the packages have different names, supply the name of the
old package followed by a percent symbol (%), then the name of the new package.
Do not add any extra whitespace between pairs of old/new package names.

Before upgrading a package, save any configuration files (such as in /etc)
that you wish to keep.  Sometimes these will be preserved, but it depends on
the package structure.  If you want to force new versions of the config files
to be installed, remove the old ones manually prior to running upgradepkg.

To upgrade in a directory other than / (such as /mnt):

   ROOT=/mnt upgradepkg package.tgz

EOF
}

# 检查/设置临时目录
TMP=$ROOT/var/log/setup/tmp
# If the $TMP directory doesn't exist, create it:
if [ ! -d $TMP ]; then
  rm -rf $TMP # make sure it's not a symlink or something stupid
  mkdir $TMP
  chmod 700 $TMP # no need to leave it open
fi

# 此脚本需要将 umask 设置为 022
umask 022

# 如果定义了 ROOT 环境变量, 则将它 export
# 如果不 export 的话, 那么这个环境变量在此脚本的子进程中无效,
# 所以将其 export, 以便对 installpkg 和 removepkg 起作用
if [ -d "$ROOT" ]; then
  export ROOT
fi

# 无参数运行或者是使用了 --help 参数, 显示帮助信息
if [ "$1" = "" -o "$1" = "--help" -o "$1" = "-?" ]; then
  usage;
  exit 1;
fi

# 参数处理, 所有支持的参数必须在包名称前, 遇到第一个不支持的参数, 则认为后面的
# 都是包名称
while [ 0 ]; do
  if [ "$1" = "--no-paranoia" ]; then
    # no paranoia 模式, 默认模式下, upgradepkg 在升级完软件后还会再次用 installpkg
    # 来安装一次刚升级的软件, no paranoia 模式下不进行这一步处理
    NOT_PARANOID="true"
    shift 1
  elif [ "$1" = "--install-new" ]; then
    # 如果要升级的软件包并非是已安装的软件, 那么使用 installpkg 对其进行安装
    # 默认情况下是忽略这种软件的
    INSTALL_NEW="yes"
    shift 1
  elif [ "$1" = "--reinstall" ]; then
    # 升级版和原先安装的版本相同时重新安装
    # 默认情况下是忽略这种软件的
    REINSTALL="true"
    shift 1
  elif [ "$1" = "--verbose" -o "$1" = "-v" ]; then
    # 详细模式, 显示更多的信息
    VERBOSE="verbose"
    shift 1
  elif [ "$1" = "--dry-run" ]; then
    # 只显示哪些软件会被安装, 或者是升级, 而不实际执行这样的操作
    DRY_RUN="true"
    shift 1
  else # 没有以上几种参数, 退出参数处理循环
    break;
  fi
done

# 获取软件名, 与 installpkg 中的相同
package_name() {
  STRING=`basename $1 .tgz`
  # Check for old style package name with one segment:
  if [ "`echo $STRING | cut -f 1 -d -`" = "`echo $STRING | cut -f 2 -d -`" ]; then
    echo $STRING
  else # has more than one dash delimited segment
    # Count number of segments:
    INDEX=1
    while [ ! "`echo $STRING | cut -f $INDEX -d -`" = "" ]; do
      INDEX=`expr $INDEX + 1`
    done
    INDEX=`expr $INDEX - 1` # don't include the null value
    # If we don't have four segments, return the old-style (or out of spec) package name:
    if [ "$INDEX" = "2" -o "$INDEX" = "3" ]; then
      echo $STRING
    else # we have four or more segments, so we'll consider this a new-style name:
      NAME=`expr $INDEX - 3`
      NAME="`echo $STRING | cut -f 1-$NAME -d -`"
      echo $NAME
      # cruft for later ;)
      #VER=`expr $INDEX - 2`
      #VER="`echo $STRING | cut -f $VER -d -`"
      #ARCH=`expr $INDEX - 1`
      #ARCH="`echo $STRING | cut -f $ARCH -d -`"
      #BUILD="`echo $STRING | cut -f $INDEX -d -`"
    fi
  fi
}

ERRCODE=0

# 主循环
# 将参数处理循环处理后的参数作为包名称对(packagename 或者是 new%old)
# 来进行处理
while [ ! "$1" = "" ]; do

# 将 new%old 形式的参数分割开, 分别存入 NEW, OLD 变量
# 如果不是 new%old 的形式, 而只是 new 的形式, cut -f1 和 cut -f2 会输出相同的内容
# 后面会用 OLD 查找旧包, 从而把 OLD 变为真正的已安装的版本号,
# 所以不会造成新旧软件相同的误判
OLD=`echo $1 | cut -f 1 -d '%'`
NEW=`echo $1 | cut -f 2 -d '%'`
INCOMINGDIR=`dirname $NEW` # 新软件包的存放路径
OLD=`basename $OLD .tgz`
NEW=`basename $NEW .tgz`

# 取得旧的软件包的基础包名称
SHORT="`package_name $OLD`"
# 如果没有此基础包名称的记录文件, 则查找其它有此相同的基础包名称的包记录文件
# 这意味着哪怕你指定了错的旧包文件名称, 也无所谓, 实例:
#
# 假定你安装了 bash-3.1.017-i486-2 你想升级到 bash-3.2.0-i486-1, 那么你用
# upgradpkg bash-3.1.000-i486-1%bash-3.2.0-i486-1 来升级也无所谓,
# 这段代码会查找 $ROOT/var/log/packages/bash, 如果没有, 就查找
# $ROOT/var/log/packages/bash*, 然后在里面找到一个基础名称为 bash 的记录项, 把它当作旧软件名
# 虽然这个地方只是找到了一个, 但是这个只是为了开始输出信息使用的,
# 后面实际操作的部分是删除所有符合条件的旧包的
if [ ! -r $ROOT/var/log/packages/$OLD ]; then
  if ls $ROOT/var/log/packages/$SHORT* 1> /dev/null 2> /dev/null ; then
    for installed_package in $ROOT/var/log/packages/$SHORT* ; do
      if [ "`package_name $installed_package`" = "$SHORT" ]; then
        OLD="`basename $installed_package`"
        break
      fi
    done
  fi
fi

# 如果还找不到旧包的安装记录, 那么根据 INSTALL_NEW 的设定来决定是否安装
# 输出信息会根据 DRY_RUN 的设定而有所不同
if [ ! -r $ROOT/var/log/packages/$OLD ]; then
  if [ ! "$INSTALL_NEW" = "yes" ]; then
    if [ "$DRY_RUN" = "true" ]; then
      echo "$OLD would not be upgraded (no installed package named $SHORT)."
    else
      echo
      echo "Error:  there is no installed package named $OLD."
      echo "        (looking for $ROOT/var/log/packages/$OLD)"
      echo
    fi
    ERRCODE=1
  else # 使用了 --install-new 参数, 使用 installpkg 来安装新软件
    if [ "$DRY_RUN" = "true" ]; then
      echo "$NEW would be installed (new package)."
    else
      cat << EOF

+==============================================================================
| Installing new package $INCOMINGDIR/$NEW.tgz
+==============================================================================

EOF
      installpkg $INCOMINGDIR/$NEW.tgz
    fi
  fi
  # 忽略了这一个, 处理下一个
  shift 1
  continue;
elif [ ! -r "$INCOMINGDIR/$NEW.tgz" ]; then # 如果新软件包文件不存在, 给出提示
  if [ "$DRY_RUN" = "true" ]; then
    echo "$NEW incoming package not found (command line)."
  else
    echo
    echo "Error:  incoming package $INCOMINGDIR/$NEW.tgz not found."
    echo
  fi
  # 忽略了这一个, 处理下一个
  shift 1
  ERRCODE=1
  continue;
fi

# 没有 --reinstall 参数, 则比较新旧软件包的名称(长名称 name-version-arch-build)
# 如果两者相同, 则输出提示, 处理下一个包, 根据 DRY_RUN 设定情况输出有所不同
if [ ! "$REINSTALL" = "true" ]; then
  if [ "$OLD" = "$NEW" ]; then
    if [ "$DRY_RUN" = "true" ]; then
      echo "$NEW would be skipped (already installed)."
    else
      cat << EOF

+==============================================================================
| Skipping package $NEW (already installed)
+==============================================================================

EOF
    fi
    shift 1
    continue;
  fi
fi

TIMESTAMP=`date +%Y-%m-%d,%T` # 时间戳
SHORT="`package_name $OLD`"
if [ "$DRY_RUN" = "true" ]; then # DRY_RUN 模式, 只提示, 不操作
  echo -n "$NEW would upgrade: "
  for installed_package in $ROOT/var/log/packages/$SHORT* ; do
  if [ "`package_name $installed_package`" = "$SHORT" ]; then # 显示所有符合旧软件的基础名称的包
    echo -n "`basename $installed_package .tgz` "
  fi
  done
  echo
  shift 1
  continue
fi
# 在 $ROOT/var/log/packages 下面查找与要卸载的软件的基础软件名相同的包记录,
# 在其名称后加上 -upgraded-$TIMESTAMP, 以便后面统一通过此特征来进行删除
for installed_package in $ROOT/var/log/packages/$SHORT* ; do
  if [ "`package_name $installed_package`" = "$SHORT" ]; then
    mv $installed_package ${installed_package}-upgraded-$TIMESTAMP
  fi
done
# 与上面的部分相似, 这个地方处理 $ROOT/var/log/scripts 下面的脚本
for installed_script in $ROOT/var/log/scripts/$SHORT* ; do
  if [ "`package_name $installed_script`" = "$SHORT" ]; then
    if [ -r $installed_script ]; then
      mv $installed_script ${installed_script}-upgraded-$TIMESTAMP
    fi
  fi
done

cat << EOF

+==============================================================================
| Upgrading $OLD package using $INCOMINGDIR/$NEW.tgz
+==============================================================================

EOF

# 先安装新软件
# 这个地方的 if 语句写错了
# 这样写的话就表示 VERBOSE 模式下不显示 Pre-installing package $NEW... 了
# 而 VERBOSE 模式应该更详细
# 所以应该写成 if [ ! "VERBOSE" = "verbose" ]; then
if [ "$VERBOSE" = "verbose" ]; then
  installpkg $INCOMINGDIR/$NEW.tgz
  RETCODE=$?
else
  echo "Pre-installing package $NEW..."
  installpkg $INCOMINGDIR/$NEW.tgz 1> /dev/null
  RETCODE=$?
fi
# installpkg 返回错误码, 表示新软件无法安装, 显示提示信息, 处理下一个包
if [ ! $RETCODE = 0 ]; then
  echo "ERROR:  Package $INCOMINGDIR/$NEW.tgz did not install"
  echo "correctly.  You may need to reinstall your old package"
  echo "to avoid problems.  Make sure the new package is not"
  echo "corrupted."
  sleep 30
  # 忽略此软件包, 处理下一个
  shift 1
  continue;
fi

# 将找到的旧软件包全部删除, 如果是非 VERBOSE 模式, 则过滤一些输出
# 这个地方对 ROOT 是否目录做了判断, 其实上面一直接都没判断,
# 如果 ROOT 真的不是目录的话早就出问题了
if [ -d "$ROOT" ]; then
  ( cd $ROOT/var/log/packages
    for rempkg in *-$TIMESTAMP ; do
      if [ "$VERBOSE" = "verbose" ]; then
        ROOT=$ROOT removepkg $rempkg
      else
        ROOT=$ROOT removepkg $rempkg | grep -v "Skipping\." | grep -v "Removing files:"
      fi
    done
  )
else
  ( cd /var/log/packages
    for rempkg in *-$TIMESTAMP ; do
      if [ "$VERBOSE" = "verbose" ]; then
        removepkg $rempkg
      else
        removepkg $rempkg | grep -v "Skipping\." | grep -v "Removing files:"
      fi
    done
  )
fi
echo

# 非 no paranoia 模式, 再次使用 installpkg 来安装一次新的软件包
# 这段代码首次出现于 slackware 8.0 的 upgradepkg 中, 在 slackware 8.0 的 Changelog.txt
# 中有这样一段相关的说明:
#    Patch upgradepkg to "double install" packages by default.  Note that you
#    might get an error if you use upgradepkg to update itself, but the
#    installed package will be ok.  I wouldn't give upgradegpkg a long list
#    of things to upgrade that includes hdsetup.tgz, though, or it will stop
#    when it reaches that package.
if [ ! "$NOT_PARANOID" = "true" ]; then
  installpkg $INCOMINGDIR/$NEW.tgz
fi

echo "Package $OLD upgraded with new package $INCOMINGDIR/$NEW.tgz."
ERRCODE=0

# 处理下一个
shift 1

done

if [ ! "$DRY_RUN" = "true" ]; then
  echo
fi
exit $ERRCODE

分类:

评论

  预览后可提交