剖析slackware pkgtools之installpkg篇

作者: 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篇》

0%