大头龙仔Blog

A strong man can save himself. A great man can save another.

剖析slackware Pkgtools之installpkg篇

| Comments

作者: slackcode pkgtools版本: pkgtools-12.1.0-noarch-7.tgz
我并不是什么高手牛人,我只是路过,觉得好玩,所以拿出来看看,希望与大家分享,共同学习
如有转载,请注明出处:)
———————————————————-
installpkg源文件请到这里: http://darkstar.ist.utl.pt/pub/slackware/slackware-12.2/source/a/pkgtools/scripts/installpkg

# If installpkg encounters a problem, it will return a non-zero error code.
# If it finds more than one problem (i.e. with a list of packages) you'll only
# hear about the most recent one. :)

# 1 = tar returned error code
# 2 = failed ‘gzip -l package’
# 3 = does not end in .tgz
# 4 = not a file
# 99 = user abort from menu mode
EXITSTATUS=0 installpkg执行结束后的返回值, 即$?

# So that we know what to expect...
umask 022        # 当前权限 与 (777 - 022)
TAR=tar-1.13
$TAR --help 1> /dev/null 2> /dev/null
if [ ! $? = 0 ]; then
    TAR=tar
fi
这里是判断$TAR –help的返回值,命令执行成功一般都为0,失败为非0,$?即为这个返回值
if [ ! "`LC_MESSAGES=C $TAR --version`" = "tar (GNU tar) 1.13

Copyright (C) 1988, 92,93,94,95,96,97,98, 1999 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Written by John Gilmore and Jay Fenlason.” ]; then
echo “WARNING: pkgtools are unstable with tar > 1.13.”
echo ” You should provide a "tar-1.13" in your \$PATH.”
sleep 5
fi installpkg定义tar-1.13为稳定版本, slackware 12.2有tar-1.13和tar-1.16.1
再看看tar是哪个版本
ls -l /bin/tar*
-rwxr-xr-x 1 root root 233196 2006-12-14 13:37 /bin/tar*
-rwxr-xr-x 1 root root 115036 2006-12-14 13:37 /bin/tar-1.13*
lrwxrwxrwx 1 root root 3 2008-12-09 23:00 /bin/tar-1.16.1 -> tar
从这个看来, tar即tar-1.16.1了
if [ ! “`LC_MESSAGES=C $TAR –version | head -n 1`” = “tar (GNU tar) 1.13” ]不是更简单么?不过可能出于严谨,作者认为只有版本号对是不够的,还要判断整个version
另这个LC_MESSAGES=C是需要的,我使用系统默认的en_US发现有问题,我知道使用C就是使用127bit的ASCII码系统,至于为什么en_US会不行,希望有高人能回答一下

usage() {
 cat << EOF
Usage: installpkg [options] package_name

Installpkg is used to install a .tgz package like this:
installpkg xf_bin.tgz

options: -warn (warn if files will be overwritten, but do not install)
-root /mnt (install someplace else, like /mnt)
-infobox (use dialog to draw an info box)
-menu (confirm package installation with a menu, unless
the priority is [required] or ADD)
-ask (used with menu mode: always ask if a package should be
installed regardless of what the package’s priority is)
-priority ADD|REC|OPT|SKP (provide a priority for the entire
package list to use instead of the priority in the
tagfile)
-tagfile /somedir/tagfile (specify a different file to use
for package priorities. The default is “tagfile” in
the package’s directory)

EOF
} 这个东西大家应该很熟悉
tagfile这个东西用得比较少,如果你经常装slackware,并且想定制自己的安装过程,那么用tagfile可以省去每次都选择安装包的麻烦,可以参考:
http://www.linuxsir.org/bbs/thread117475.html
http://www.flaterco.com/kb/slackware.html
http://www.linuxsir.org/bbs/thread117475-2.html
http://www.justlinux.com/nhf/Distribution_Specific/Slackware_Linux/Slackware__Tagfiles.html

# Eliminate whitespace function:
crunch() {
  while read FOO ; do
    echo $FOO
  done
}
这个函数我测试才发现的,能去掉多余的空格,保证最多只保留一个空格
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
}
其实这段代码我不是太理解,因为我只接触过new-style name
以pkgtools-12.1.0-noarch-7.tgz为例吧,new-style名字结构为: name-ver-arch-build.tgz构成
我以前试过自己构建tgz包,但没有遵循这个命名规则,removepkg时没能正确识别这个包
总得来说,这个函数的作用是取得这个包的名字
我的疑问是为什么不直接cut -f 1 -d -呢,而要多做这么多工作?
# Set the prefix for the package database directories (packages, scripts).
ADM_DIR="$ROOT/var/log"
# If the directories don't exist, "initialize" the package database:
for PKGDBDIR in packages removed_packages removed_scripts scripts setup ; do
  if [ ! -d $ADM_DIR/$PKGDBDIR ]; then
    rm -rf $ADM_DIR/$PKGDBDIR # make sure it's not a symlink or something stupid
    mkdir -p $ADM_DIR/$PKGDBDIR
    chmod 755 $ADM_DIR/$PKGDBDIR
  fi
done
#建立包数据库,这几个目录就是pkgtools的包管理数据库啦 #包括包的安装和删除信息,一些安装和删除需要的脚本等都在这里了,这个路径也叫/var/adm/
# 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
  rm -rf $TMP # make sure it's not a symlink or something stupid
  mkdir -p $TMP
  chmod 700 $TMP # no need to leave it open
fi
建立临时目录
# usage(), exit if called with no arguments:
if [ $# = 0 ]; then
  usage;
  exit
fi
没有任何参数时
# If -warn mode was requested, produce the output and then exit:
if [ "$MODE" = "warn" ]; then
  while [ -f "$1" ]; do
    echo "#### Scanning the contents of $1..."
    mkdir -p $TMP/scan$$
    ( cd $TMP/scan$$ ; $TAR xzf - install ) < $1 2> /dev/null
    # tar xzf - install的意思是: 把$1里的install目录解压为当前目录
    # 我第一次看到这种用法, 感到很cool

if [ -r $TMP/scan$$/install/doinst.sh ]; then
if cat $TMP/scan$$/install/doinst.sh | grep ’ rm -rf ’ 1>/dev/null 2>/dev/null ; then
cat $TMP/scan$$/install/doinst.sh | grep ’ rm -rf ’ > $TMP/scan$$/install/delete
echo “The following locations will be completely WIPED OUT to allow symbolic”
echo “links to be made. (We’re talking ‘rm -rf’) These locations may be files,”
echo “or entire directories. Be sure you’ve backed up anything at these”
echo “locations that you want to save before you install this package:”
cat $TMP/scan$$/install/delete | cut -f 3,7 -d ’ ’ | tr ’ ’ ‘/’
# 对于这句的处理我觉得挺有意思, pkgtools约定的格式用法
# 向用户提示一下rm -rf的东东
fi
if [ -d $TMP/scan$$ ]; then
( cd $TMP/scan$$ ; rm -rf install ) 2> /dev/null
( cd $TMP ; rmdir scan$$ ) 2> /dev/null
fi
fi
echo “The following files will be overwritten when installing this package.”
echo “Be sure they aren’t important before you install this package:”
( $TAR tzvvf - ) < $1 | grep -v 'drwx'
echo
shift 1
done
exit
fi warn模式, 作用是模拟安装过程, 提示用户哪些操作要注意的

# Main loop:
# 好不容易, 终于来到installpkg主循环啦LOL
for package in $* ; do
  # 当你installpkg a b c d时, 那里$*就是a b c d啦, 就是传入的所有包名字

# If someone left off the .tgz, try to figure that out:
# “$package”不可读且”$package.tgz”可读, 自己加上.tgz
if [ ! -r “$package” -a -r “$package.tgz” ]; then
package=$package.tgz
fi

# “shortname” isn’t really THAT short… it’s just the full name without “.tgz”
shortname=”`basename $package .tgz`”
packagedir=”`dirname $package`”
# This is the base package name, used for grepping tagfiles and descriptions:
packagebase=”`package_name $shortname`”
# examples:
# installpkg /root/bash-3.1.017-i486-2.tgz
# shortname = bash-3.1.017-i486-2
# packagedir = /root(如果是installpkg bash-3.1.017-i486-2.tgz, 这个结果是.)
# packagebase = bash, package_name是函数, 那句英文注释说明其作用了

  # Reject package if it does not end in '.tgz':
  if [ ! -r "`dirname $package`/$shortname.tgz" ]; then
    EXITSTATUS=3
    if [ "$MODE" = "install" ]; then
      echo "Cannot install $package: package does not end in .tgz"
    fi
    continue;
  fi

# Determine package’s priority:
unset PRIORITY
if [ “$USERTAGFILE” = “” ]; then
TAGFILE=”`dirname $package`/tagfile”
else
TAGFILE=”$USERTAGFILE”
fi
if [ ! -r “$TAGFILE” ]; then
TAGFILE=/dev/null
fi
if grep “^$packagebase:” “$TAGFILE” | grep ADD > /dev/null 2> /dev/null ; then
PRIORITY=”ADD”
elif grep “^$packagebase:” “$TAGFILE” | grep REC > /dev/null 2> /dev/null ; then
PRIORITY=”REC”
elif grep “^$packagebase:” “$TAGFILE” | grep OPT > /dev/null 2> /dev/null ; then
PRIORITY=”OPT”
elif grep “^$packagebase:” “$TAGFILE” | grep SKP > /dev/null 2> /dev/null ; then
PRIORITY=”SKP”
fi
if [ “$PRIORITY” = “ADD” ]; then
PMSG=”[ADD]”
elif [ “$PRIORITY” = “REC” ]; then
PMSG=”[REC]”
elif [ “$PRIORITY” = “OPT” ]; then
PMSG=”[OPT]”
elif [ “$PRIORITY” = “SKP” ]; then
PMSG=”[SKP]”
else
PMSG=””
fi 上面的$packagebase派上用场了, 还是tagfile, 之前发过一些关于tagfile的介绍, 我本人没用过tagfile来安装过
ADD: Install the package.
REC: Prompt whether to install the package, default yes.
OPT: Prompt whether to install the package, default no.
SKP: Do not install the package.

  # Locate package description file:
  DESCRIPTION="/dev/null"
  # First check the usual locations outside the package, since this is faster:
  # 现在基本都是带有txt文件,像bash-3.1.017-i486-2.txt
  for file in $packagedir/disk* $packagedir/package_descriptions $packagedir/$shortname.txt $packagedir/$packagebase.txt ; do
    if grep "^$packagebase:" "$file" 1> /dev/null 2> /dev/null ; then
      DESCRIPTION="$file"
    elif grep "^$shortname:" "$file" 1> /dev/null 2> /dev/null ; then
      DESCRIPTION="$file"
    fi
  done
  # If we still don't have anything, look inside the package.  This requires a costly untar.
  if [ "$DESCRIPTION" = "/dev/null" ]; then
    # $$是当前的进程号,这样可以保证目录唯一性,下面将大量使用$$
    mkdir -p $TMP/scan$$
    ( cd $TMP/scan$$ ; $TAR xzf - install ) < $package 2> /dev/null
    if grep "^$packagebase:" "$TMP/scan$$/install/slack-desc" 1> /dev/null 2> /dev/null ; then
      DESCRIPTION="$TMP/scan$$/install/slack-desc"
    elif grep "^$shortname:" "$TMP/scan$$/install/slack-desc" 1> /dev/null 2> /dev/null ; then
      DESCRIPTION="$TMP/scan$$/install/slack-desc"
    fi
  fi

# Simple package integrity check:
if [ ! -f $package ]; then
EXITSTATUS=4
if [ “$MODE” = “install” ]; then
echo “Cannot install $package: package is not a regular file”
fi
continue;
fi

# gzip -l一下,看看成功否
gzip -l $package 1> /dev/null 2> /dev/null
if [ ! “$?” = “0” ]; thenf
EXITSTATUS=2 # failed gzip -l # failed gzip -l 不成功
if [ “$MODE” = “install” ]; then
echo “Cannot install $package: package is corrupt (failed ‘gzip -l $package’)”
fi
continue;
fi

# Collect the package information into a temp file:
COMPRESSED=`gzip -l $package | grep -v uncompressed_name | crunch | cut -f 1 -d ’ ‘`
UNCOMPRESSED=`gzip -l $package | grep -v uncompressed_name | crunch | cut -f 2 -d ’ ‘`
COMPRESSED=”`expr $COMPRESSED / 1024` K”
UNCOMPRESSED=”`expr $UNCOMPRESSED / 1024` K”
# MD5SUM=`md5sum $package | cut -f 1 -d ’ ‘`
cat $DESCRIPTION | grep “^$packagebase:” | cut -f 2- -d : | cut -b2- 1> $TMP/tmpmsg$$ 2> /dev/null
if [ “$shortname” != “$packagebase” ]; then
cat $DESCRIPTION | grep “^$shortname:” | cut -f 2- -d : | cut -b2- 1>> $TMP/tmpmsg$$ 2> /dev/null
fi
# Adjust the length here. This allows a slack-desc to be any size up to 13 lines instead of fixed at 11.
# 这些要补足12行的原因,我估计是为了显示上的美观
LENGTH=`cat $TMP/tmpmsg$$ | wc -l`
while [ $LENGTH -lt 12 ]; do
echo >> $TMP/tmpmsg$$
LENGTH=`expr $LENGTH + 1`
done
echo “Size: Compressed: $COMPRESSED, uncompressed: $UNCOMPRESSED.” >> $TMP/tmpmsg$$
# For recent versions of dialog it is necessary to add \n to the end of each line
# or it will remove repeating spaces and mess up our careful formatting:
cat << EOF > $TMP/controlns$$
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
EOF
# 一行行合并这两个文件
paste -d “” $TMP/tmpmsg$$ $TMP/controlns$$ > $TMP/pasted$$
rm -f $TMP/controlns$$
mv $TMP/pasted$$ $TMP/tmpmsg$$
# Emit information to the console:
if [ “$MODE” = “install” ]; then
if [ “$PMSG” = “” ]; then
echo “Installing package $shortname… ” # 不使用tagfile,应该都进到这个
else
echo “Installing package $shortname ($PMSG)… ”
fi
echo “PACKAGE DESCRIPTION:”
cat $DESCRIPTION | grep “^$packagebase:” | uniq
if [ “$shortname” != “$packagebase” ]; then
cat $DESCRIPTION | grep “^$shortname:” | uniq
fi
elif [ “$MODE” = “infobox” -a ! “$PRIORITY” = “SKP” ]; then # install non-SKP infobox package
# infobox模式, 非SKP(install non-SKP infobox package)
dialog –title “Installing package $shortname $PMSG” –infobox “`cat $TMP/tmpmsg$$`” 0 0
elif [ “$MODE” = “menu” -a “$PRIORITY” = “ADD” -a ! “$ALWAYSASK” = “yes” ]; then # ADD overrides menu mode unless -ask was used
# menu模式, ADD, 非-ask(ADD overrides menu mode unless -ask was used)
dialog –title “Installing package $shortname $PMSG” –infobox “`cat $TMP/tmpmsg$$`” 0 0
elif [ “$MODE” = “menu” -a “$USERPRIORITY” = “ADD” ]; then # install no matter what $PRIORITY
# menu模式, installpkg指定为ADD(install no matter what $PRIORITY)
dialog –title “Installing package $shortname $PMSG” –infobox “`cat $TMP/tmpmsg$$`” 0 0
elif [ “$MODE” = “menu” -a “$PRIORITY” = “SKP” -a ! “$ALWAYSASK” = “yes” ]; then # SKP overrides menu mode unless -ask used
# menu模式, SKP, 非-ask(SKP overrides menu mode unless -ask used)
rm -f $TMP/tmpmsg$$
continue # next package 不安装,跳过
elif [ “$MODE” = “infobox” -a “$PRIORITY” = “SKP” -a ! “$ALWAYSASK” = “yes” ]; then # SKP overrides infobox mode, too
# infobox模式, SKP, 非-ask(SKP overrides infobox mode, too)
rm -f $TMP/tmpmsg$$
continue # next package 不安装,跳过
else # we must need a full menu:
# 构造一个带选择的dialog界面
dialog –title “Package Name: $shortname $PMSG” –menu “`cat $TMP/tmpmsg$$`” 0 0 3 \
“Yes” “Install package $shortname” \
“No” “Do not install package $shortname” \
“Quit” “Abort software installation completely” 2> $TMP/reply$$
if [ ! $? = 0 ]; then
echo “No” > $TMP/reply$$
fi
REPLY=”`cat $TMP/reply$$`”
rm -f $TMP/reply$$ $TMP/tmpmsg$$
if [ “$REPLY” = “Quit” ]; then
exit 99 # EXIT STATUS 99 = ABORT! 退出啦,返回值是99
elif [ “$REPLY” = “No” ]; then
continue # skip the package 跳过这个包
fi
fi dialog部分理解不是太好,觉得逻辑有点乱,加上自己平时都直接installpk的,不带参数
理解的兄弟请留言一下

  # Test tarball integrity, and make sure we're not installing files on top of existing symbolic links:
  # 这个地方测试一下这个tgz包能不能解压
  $TAR tzf $package 1> $TMP/tmplist$$ 2> /dev/null
  TARERROR=$?
  if [ ! "$TARERROR" = "0" ]; then
    EXITSTATUS=1 # tar file corrupt
    if [ "$MODE" = "install" ]; then
      echo "Unable to install $package: tar archive is corrupt (tar returned error code $TARERROR)"
    fi
    rm -f $TMP/tmplist$$
    continue
  fi
  # 第一次看用while能这么传进去的, 这里是看看将要安装的文件
  # 在当前系统的是否有同名并且为符号链接的, 要删掉
  cat $TMP/tmplist$$ | grep -v "/$" | while read file ; do
    if [ -L "$ROOT/$file" ]; then
      rm -f "$ROOT/$file"
    fi
  done
  rm -f $TMP/tmplist$$

# Write the package file database entry and install the package:
# 建立数据库, 都是由以shortname为文件名的文本文件组成
# 就在/var/adm/packages或/var/log/packages下
# 所以在这里你可以查得当前系统安装的软件包信息
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 “PACKAGE MD5SUM: $MD5SUM” >> $ADM_DIR/packages/$shortname
echo “PACKAGE DESCRIPTION:” >> $ADM_DIR/packages/$shortname
cat $DESCRIPTION | grep “^$packagebase:” >> $ADM_DIR/packages/$shortname 2> /dev/null
if [ “$shortname” != “$packagebase” ]; then
cat $DESCRIPTION | grep “^$shortname:” >> $ADM_DIR/packages/$shortname 2> /dev/null
fi
echo “FILE LIST:” >> $ADM_DIR/packages/$shortname
( cd $ROOT/ ; $TAR -xzlUpvf - ) < $package >> $TMP/$shortname 2> /dev/null
# 这个tar参数好长啊, 常用的不说了, 说不常用的, 这个命令在tar-1.16.1有错误, tar-1.13没有问题
# 初步估计是-U参数问题, tar-1.16.1的–help没有-U说明
# 另外, 要注意的是, 如果installpkg
# -l, –one-file-system stay in local file system when creating archive
# 这个东东好像比较难理解,可以参考:http://www.delorie.com/gnu/docs/tar/tar_97.html
# 基本意思就是只解压在同一文件系统下, 至于这样做的意义, 个人认为是出于包完整性的考虑
# -U, –unlink-first remove each file prior to extracting over it, 先删除文件, 再解压
# -p, –same-permissions extract all protection information, 保持文件属性
if [ “`cat $TMP/$shortname | grep ‘^./’ | wc -l | tr -d ’ ‘`” = “1” ]; then
# Good. We have a package that meets the Slackware spec.
# tgz包里有个.目录, 不知这个算不算tgz包的特征啊, 从上面这句英文来看就是了
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…
# 这段英文有意思, 哈
echo ‘./’ >> $ADM_DIR/packages/$shortname
cat $TMP/$shortname | grep -v ‘^./$’ | cut -b3- >> $ADM_DIR/packages/$shortname
# 这一行我理解不了,不知为什么多了cut
fi
rm -f $TMP/$shortname

if [ -x /sbin/ldconfig ]; then
/sbin/ldconfig # 更动态链接绑定, 新加入的so文件需要这个命令来使其生效
fi
if [ -f $ROOT/install/doinst.sh ]; then
if [ “$MODE” = “install” ]; then
echo “Executing install script for $shortname…”
fi
( cd $ROOT/ ; sh install/doinst.sh -install; ) # 执行安装包的安装脚本
fi
# Clean up the mess…
if [ -d $ROOT/install ]; then # install是目录
if [ -r $ROOT/install/doinst.sh ]; then # doinst.sh可读
# 备份这个doinst.sh
cp $ROOT/install/doinst.sh $ADM_DIR/scripts/$shortname
chmod 755 $ADM_DIR/scripts/$shortname
fi
# /install/doinst.sh and /install/slack-* are reserved locations for the package system.
( cd $ROOT/install ; rm -f doinst.sh slack-* 1> /dev/null 2><1 ) rmdir $ROOT/install 1> /dev/null 2><1
fi
# If we used a scan directory, get rid of it:
if [ -d “$TMP/scan$$” ]; then
rm -rf “$TMP/scan$$”
fi
rm -f $TMP/tmpmsg$$ $TMP/reply$$
if [ “$MODE” = “install” ]; then
echo
fi
done

exit $EXITSTATUS 感觉像流水账,但回头看看,这不就是shell脚本么?
其中的shell指令也有些巧妙,当我细读这个脚本时体会到了
边读边学,还看到了一些tar和while的不常用用法,见识多了,但还是有不懂的地方,希望热心的朋友们指出和提出意见
读完installpkg后,更感觉到slackware的slack 下一篇是《剖析slackware pkgtools之upgradepkg篇》

Comments