Solaris OS 中实现的 System V 打包的完整功能提供了一个用于安装软件产品的强大工具。作为软件包设计者,您可以利用这些功能。不属于 Solaris OS 的软件包(非随附软件包)可以使用类机制来定制服务器/客户机安装。可针对管理员的需要设计可重定位的软件包。复杂产品可以通过能够自动解决软件包相关性问题的复合软件包集合形式提供。升级和修补可由软件包设计者定制。修补的软件包可以按照与未修补的软件包相同的方式提供,并且产品中还可以包括回退归档。
以下是本章中概述信息的列表。
您可以使用多种方法指定软件包的安装位置,而且很重要的一点是能够在安装时动态更改安装基本位置。如果正确实现了此功能,管理员便可以轻松安装多个版本和多个体系结构。
本节首先讨论常用方法,然后讨论能够改进异构系统上的安装的途径。
负责安装软件包的管理员可以使用管理文件来控制软件包安装。然而,作为软件包设计者,您需要了解有关管理文件的信息,并了解管理员如何能够改变您计划的软件包安装。
管理文件告诉 pkgadd 命令是否执行它通常执行的任何检查或提示。因此,管理员在使用管理文件之前应该充分了解软件包的安装过程和涉及的脚本。
缺省基本管理文件随 SunOS 操作系统一起提供,位于 /var/sadm/install/admin/default 中。该文件建立了与软件产品安装有关的最基本级别的管理策略。随操作系统一起提供的该文件如下所示:
#ident "@(#)default 1.4 92/12/23 SMI" /* SVr4.0 1.5.2.1 */ mail= instance=unique partial=ask runlevel=ask idepend=ask rdepend=ask space=ask setuid=ask conflict=ask action=ask basedir=default |
管理员可以编辑此文件以建立新的缺省行为,或者创建一个不同的管理文件并使用 pkgadd 命令的 -a 选项指定其存在。
在一个管理文件中可以定义 11 个参数,但并非所有参数都必须定义。有关更多信息,请参见 admin(4)。
basedir 参数指定在安装软件包时如何派生基目录。大多数管理员都将此参数保留为 default,但可以将 basedir 设置为以下值之一:
ask,表示始终要求管理员提供基目录
绝对路径名
包含 $PKGINST 构造的绝对路径名,表示始终安装到派生于软件包实例的基目录
如果带有参数 -a none 调用了 pkgadd 命令,则该命令始终要求管理员提供基目录。遗憾的是,这还会将文件中的所有参数设置为缺省值 quit,而这可能会导致其他问题。
管理员可使用管理文件来控制系统上正安装的所有软件包。遗憾的是,软件包设计者经常提供一个替代的缺省管理文件,而没有考虑管理员的愿望。
软件包设计者有时会提供一个替代管理文件,以便他们自己(而不是管理员)能够控制软件包的安装。由于缺省管理文件中的 basedir 条目会覆盖所有其他基目录,因此它提供了一种在安装时选择适当基目录的简单方法。在早于 Solaris 2.5 发行版的所有 Solaris OS 版本中,这种方法被认为是控制基目录的最简单方法。
然而,您必须接受管理员的有关产品安装的希望。提供一个临时的缺省管理文件以便控制安装会导致管理员觉得不受信任。您应该使用 request 脚本和 checkinstall 脚本在管理员的监督下控制这些安装。如果 request 脚本如实地使管理员参与安装过程,System V 打包过程将同时满足管理员和软件包设计者的需求。
pkginfo 文件都必须以如下所示的条目形式包括一个缺省基目录:
BASEDIR=absolute_path |
这只是缺省基目录,可由管理员在安装期间更改。
尽管某些软件包需要多个基目录,但使用此参数定位软件包的好处是,当安装开始的时候,能够保证基目录作为一个有效的目录就位并可写。服务器和客户机的基目录的正确路径以保留环境变量的形式供所有过程脚本使用,并且 pkginfo -r SUNWstuf 命令显示软件包的当前安装基本位置。
在 checkinstall 脚本中,BASEDIR 即是 pkginfo 文件中定义的该参数(该参数尚未根据条件赋值)。为了检查目标基目录,需要使用 $ {PKG_INSTALL_ROOT} $BASEDIR 构造。这意味着 request 或 checkinstall 脚本可以在安装环境下更改 BASEDIR 的值,并且可以获得可预测的结果。当调用 preinstall 脚本时,BASEDIR 参数是指向目标系统上实际基目录的完全根据条件设置的指针,即使该系统是客户机也是如此。
对于不同的 SunOS 操作系统发行版,request 脚本以不同方式利用 BASEDIR 参数。为了在 request 脚本中测试 BASEDIR 参数,应该使用下面的代码来确定正在使用的实际基目录。
# request script constructs base directory if [ ${CLIENT_BASEDIR} ]; then LOCAL_BASE=$BASEDIR else LOCAL_BASE=${PKG_INSTALL_ROOT}$BASEDIR fi |
如果一个软件包需要多个基目录,您可以使用参数化路径名建立这些目录。此方法已经相当流行,尽管它具有以下缺点。
具有参数化路径名的软件包的行为方式通常类似于绝对软件包,但 pkgadd 命令会像处理可重定位的软件包一样处理该软件包。必须定义 BASEDIR 参数(即使没有使用)。
管理员无法使用 System V 实用程序确定软件包的安装基本位置(pkginfo -r 命令不起作用)。
管理员无法使用既定的方法重定位软件包(它称为可重定位的软件包,但是其行为方式与绝对软件包相同)。
多体系结构或多版本的安装要求对每个目标基目录进行意外事件计划,这通常意味着使用多个复杂的类操作脚本。
尽管确定基目录的参数是在 pkginfo 文件中定义的,但可以被 request 脚本修改。这是此方法广受欢迎的主要原因之一。然而,此方法的缺点会长期存在,您应该在迫不得已的情况下才考虑使用此配置。
# pkginfo file PKG=SUNWstuf NAME=software stuff ARCH=sparc VERSION=1.0.0,REV=1.0.5 CATEGORY=application DESC=a set of utilities that do stuff BASEDIR=/ EZDIR=/usr/stuf/EZstuf HRDDIR=/opt/SUNWstuf/HRDstuf VENDOR=Sun Microsystems, Inc. HOTLINE=Please contact your local service provider EMAIL= MAXINST=1000 CLASSES=none PSTAMP=hubert980707141632 |
: 1 1758 1 d none $EZDIR 0775 root bin 1 f none $EZDIR/dirdel 0555 bin bin 40 773 751310229 1 f none $EZDIR/usrdel 0555 bin bin 40 773 751310229 1 f none $EZDIR/filedel 0555 bin bin 40 773 751310229 1 d none $HRDDIR 0775 root bin 1 f none $HRDDIR/mksmart 0555 bin bin 40 773 751310229 1 f none $HRDDIR/mktall 0555 bin bin 40 773 751310229 1 f none $HRDDIR/mkcute 0555 bin bin 40 773 751310229 1 f none $HRDDIR/mkeasy 0555 bin bin 40 773 751310229 1 d none /etc ? ? ? 1 d none /etc/rc2.d ? ? ? 1 f none /etc/rc2.d/S70dostuf 0744 root sys 450 223443 1 i pkginfo 348 28411 760740163 1 i postinstall 323 26475 751309908 1 i postremove 402 33179 751309945 1 i preinstall 321 26254 751310019 1 i preremove 320 26114 751309865 |
如有必要,任何具有多个版本或多种体系结构的软件包都应该设计为遍历基目录。遍历基目录意味着如果基目录中已经存在要安装的软件包的以前版本或不同体系结构,则要安装的软件包会解决此问题,方法可能是使用稍微不同的名称创建一个新的基目录。Solaris 2.5 和兼容发行版中的 request 和 checkinstall 脚本能够修改 BASEDIR 环境变量。对于 Solaris OS 的任何更早版本,情况则不是这样的。
即使在 Solaris OS 的较早版本中,request 脚本也有权在安装基本位置中重新定义目录。request 脚本可以采用仍然支持多数管理首选项的方法完成此操作。
尽管您可以为各种软件包选择能够保证对某个体系结构和版本唯一的基目录,但这会导致目录分层结构中存在多余的级别。例如,对于为基于 SPARC 和 x86 的处理器设计的产品,您可能会根据处理器和版本组织基目录,如下所示。
基目录 |
版本和处理器 |
---|---|
/opt/SUNWstuf/sparc/1.0 |
1.0 版,SPARC |
/opt/SUNWstuf/sparc/1.2 |
1.2 版,SPARC |
/opt/SUNWstuf/x86/1.0 |
1.0 版,x86 |
这样做完全可以而且有效,但是您处理名称和编号的方式就好像它们对管理员有意义一样。一种更好的方法是在向管理员进行解释并获取权限之后自动完成此工作。
这意味着您可以在软件包中完成全部工作,而无需要求管理员手动完成。您可以在 postinstall 脚本中任意指定基目录,然后透明地建立适当的客户机链接。您还可以在 postinstall 脚本中使用 pkgadd 命令在客户机上安装软件包的全部或一部分。您甚至可以咨询管理员哪些用户或客户机需要了解此软件包,并自动更新 PATH 环境变量和 /etc 文件。只要软件包在安装时执行的所有操作能够在删除时撤消,那么该方法是完全可以接受的。
您可以利用两种方法在安装时控制基目录。第一种方法最适合于仅能安装在 Solaris 2.5 和兼容发行版上的新软件包;它为管理员提供非常有用的数据,支持多个已安装的版本和体系结构,而只需要完成最少的特殊工作。第二种方法可由任何软件包使用,并且利用 request 脚本所固有的对于生成参数的控制来确保安装成功。
checkinstall 脚本可以在安装时选择适当的基目录,这意味着可以将基目录放置在目录树中非常低的位置。此示例按顺序递增基目录,从而得到如下形式的目录:/opt/SUNWstuf、/opt/SUNWstuf.1 和 /opt/SUNWstuf.2。管理员可以使用 pkginfo 命令确定在每个基目录中所安装的体系结构和版本。
如果 SUNWstuf 软件包(包含一组执行具体工作的实用程序)使用此方法,则它的 pkginfo 和 pkgmap 文件将如下所示。
# pkginfo file PKG=SUNWstuf NAME=software stuff ARCH=sparc VERSION=1.0.0,REV=1.0.5 CATEGORY=application DESC=a set of utilities that do stuff BASEDIR=/opt/SUNWstuf VENDOR=Sun Microsystems, Inc. HOTLINE=Please contact your local service provider EMAIL= MAXINST=1000 CLASSES=none daemon PSTAMP=hubert990707141632 |
: 1 1758 1 d none EZstuf 0775 root bin 1 f none EZstuf/dirdel 0555 bin bin 40 773 751310229 1 f none EZstuf/usrdel 0555 bin bin 40 773 751310229 1 f none EZstuf/filedel 0555 bin bin 40 773 751310229 1 d none HRDstuf 0775 root bin 1 f none HRDstuf/mksmart 0555 bin bin 40 773 751310229 1 f none HRDstuf/mktall 0555 bin bin 40 773 751310229 1 f none HRDstuf/mkcute 0555 bin bin 40 773 751310229 1 f none HRDstuf/mkeasy 0555 bin bin 40 773 751310229 1 d none /etc ? ? ? 1 d none /etc/rc2.d ? ? ? 1 f daemon /etc/rc2.d/S70dostuf 0744 root sys 450 223443 1 i pkginfo 348 28411 760740163 1 i postinstall 323 26475 751309908 1 i postremove 402 33179 751309945 1 i preinstall 321 26254 751310019 1 i preremove 320 26114 751309865 1 i i.daemon 509 39560 752978103 1 i r.daemon 320 24573 742152591 |
假定 SUNWstuf 的 x86 版本已经安装在服务器上的 /opt/SUNWstuf 中。当管理员使用 pkgadd 命令安装 SPARC 版本时,request 脚本需要检测是否存在 x86 版本并且与管理员就安装问题进行交互。
可在 checkinstall 脚本中遍历基目录而无需管理员交互,但是,如果此类任意操作的发生过于频繁,管理员会在此过程中失去信心。
软件包的用于处理此情形的 request 脚本和 checkinstall 脚本可能如下所示。
# request script for SUNWstuf to walk the BASEDIR parameter. PATH=/usr/sadm/bin:${PATH} # use admin utilities GENMSG="The base directory $LOCAL_BASE already contains a \ different architecture or version of $PKG." OLDMSG="If the option \"-a none\" was used, press the \ key and enter an unused base directory when it is requested." OLDPROMPT="Do you want to overwrite this version? " OLDHELP="\"y\" will replace the installed package, \"n\" will \ stop the installation." SUSPEND="Suspending installation at user request using error \ code 1." MSG="This package could be installed at the unused base directory $WRKNG_BASE." PROMPT="Do you want to use to the proposed base directory? " HELP="A response of \"y\" will install to the proposed directory and continue, \"n\" will request a different directory. If the option \"-a none\" was used, press the key and enter an unused base directory when it is requested." DIRPROMPT="Select a preferred base directory ($WRKNG_BASE) " DIRHELP="The package $PKG will be installed at the location entered." NUBD_MSG="The base directory has changed. Be sure to update \ any applicable search paths with the actual location of the \ binaries which are at $WRKNG_BASE/EZstuf and $WRKNG_BASE/HRDstuf." OldSolaris="" Changed="" Suffix="0" # # Determine if this product is actually installed in the working # base directory. # Product_is_present () { if [ -d $WRKNG_BASE/EZstuf -o -d $WRKNG_BASE/HRDstuf ]; then return 1 else return 0 fi } if [ ${BASEDIR} ]; then # This may be an old version of Solaris. In the latest Solaris # CLIENT_BASEDIR won't be defined yet. In older version it is. if [ ${CLIENT_BASEDIR} ]; then LOCAL_BASE=$BASEDIR OldSolaris="true" else # The base directory hasn't been processed yet LOCAL_BASE=${PKG_INSTALL_ROOT}$BASEDIR fi WRKNG_BASE=$LOCAL_BASE # See if the base directory is already in place and walk it if # possible while [ -d ${WRKNG_BASE} -a Product_is_present ]; do # There is a conflict # Is this an update of the same arch & version? if [ ${UPDATE} ]; then exit 0 # It's out of our hands. else # So this is a different architecture or # version than what is already there. # Walk the base directory Suffix=`expr $Suffix + 1` WRKNG_BASE=$LOCAL_BASE.$Suffix Changed="true" fi done # So now we can propose a base directory that isn't claimed by # any of our other versions. if [ $Changed ]; then puttext "$GENMSG" if [ $OldSolaris ]; then puttext "$OLDMSG" result=`ckyorn -Q -d "a" -h "$OLDHELP" -p "$OLDPROMPT"` if [ $result="n" ]; then puttext "$SUSPEND" exit 1 # suspend installation else exit 0 fi else # The latest functionality is available puttext "$MSG" result=`ckyorn -Q -d "a" -h "$HELP" -p "$PROMPT"` if [ $? -eq 3]; then echo quitinstall >> $1 exit 0 fi if [ $result="n" ]; then WRKNG_BASE=`ckpath -ayw -d "$WRKNG_BASE" \ -h "$DIRHELP" -p "$DIRPROMPT"` else if [ $result="a" ] exit 0 fi fi echo "BASEDIR=$WRKNG_BASE" >> $1 puttext "$NUBD_MSG" fi fi exit 0 |
# checkinstall script for SUNWstuf to politely suspend grep quitinstall $1 if [ $? -eq 0 ]; then exit 3 # politely suspend installation fi exit 0 |
如果基目录仅仅是 /opt,则此方法不会非常有效。因为 /opt 难以遍历,所以此软件包必须更加准确地调用 BASEDIR。实际上,根据挂载方案的不同,可能无法实现这一点。此示例通过在 /opt 下创建一个新目录来遍历基目录,这种方法不会导致任何问题。
此示例使用 request 脚本和 checkinstall 脚本,即使 Solaris 2.5 发行版以前的版本无法运行 checkinstall 脚本。此示例中的 checkinstall 脚本用于正常停止安装,以响应一个采用 quitinstall 字符串形式的专用消息。如果此脚本在 Solaris 2.3 发行版中执行,checkinstall 脚本将被忽略,而 request 脚本将停止安装并显示错误消息。
请记住,在 Solaris 2.5 和兼容发行版之前,BASEDIR 参数是一个只读参数,不能通过 request 脚本更改。因此,如果检测到 SunOS 操作系统的早期版本(通过测试 CLIENT_BASEDIR 条件环境变量),request 脚本只有两种选择-继续或退出。
如果您的软件产品可能安装在 SunOS 操作系统的早期版本上,则 request 脚本需要完成所有必要的工作。还可以使用此方法处理多个目录。如果需要使用额外的目录,仍然需要将这些包括在单个基目录下,以便提供易于管理的产品。尽管 BASEDIR 参数没有提供最新 Solaris 发行版中的粒度级别,您的软件包仍然可以使用 request 脚本来处理参数化路径,从而遍历基目录。pkginfo 和 pkgmap 文件可能如下所示。
# pkginfo file PKG=SUNWstuf NAME=software stuff ARCH=sparc VERSION=1.0.0,REV=1.0.5 CATEGORY=application DESC=a set of utilities that do stuff BASEDIR=/opt SUBBASE=SUNWstuf VENDOR=Sun Microsystems, Inc. HOTLINE=Please contact your local service provider EMAIL= MAXINST=1000 CLASSES=none daemon PSTAMP=hubert990707141632 |
: 1 1758 1 d none $SUBBASE/EZstuf 0775 root bin 1 f none $SUBBASE/EZstuf/dirdel 0555 bin bin 40 773 751310229 1 f none $SUBBASE/EZstuf/usrdel 0555 bin bin 40 773 751310229 1 f none $SUBBASE/EZstuf/filedel 0555 bin bin 40 773 751310229 1 d none $SUBBASE/HRDstuf 0775 root bin 1 f none $SUBBASE/HRDstuf/mksmart 0555 bin bin 40 773 751310229 1 f none $SUBBASE/HRDstuf/mktall 0555 bin bin 40 773 751310229 1 f none $SUBBASE/HRDstuf/mkcute 0555 bin bin 40 773 751310229 1 f none $SUBBASE/HRDstuf/mkeasy 0555 bin bin 40 773 751310229 1 d none /etc ? ? ? 1 d none /etc/rc2.d ? ? ? 1 f daemon /etc/rc2.d/S70dostuf 0744 root sys 450 223443 1 i pkginfo 348 28411 760740163 1 i postinstall 323 26475 751309908 1 i postremove 402 33179 751309945 1 i preinstall 321 26254 751310019 1 i preremove 320 26114 751309865 1 i i.daemon 509 39560 752978103 1 i r.daemon 320 24573 742152591 |
此示例并不完善。pkginfo -r 命令为安装基本位置返回 /opt,其含义相当模糊。许多软件包位于 /opt 中,但起码它是一个有意义的目录。就像上一个示例一样,接下来的这个示例完全支持多个体系结构和版本。request 脚本可根据特定软件包的需要进行调整,并可解析任何适用的相关性。
# request script for SUNWstuf to walk a parametric path PATH=/usr/sadm/bin:${PATH} # use admin utilities MSG="The target directory $LOCAL_BASE already contains \ different architecture or version of $PKG. This package \ could be installed at the unused target directory $WRKNG_BASE." PROMPT="Do you want to use to the proposed directory? " HELP="A response of \"y\" will install to the proposed directory \ and continue, \"n\" will request a different directory. If \ the option \"-a none\" was used, press the <RETURN> key and \ enter an unused base directory when it is requested." DIRPROMPT="Select a relative target directory under $BASEDIR/" DIRHELP="The package $PKG will be installed at the location entered." SUSPEND="Suspending installation at user request using error \ code 1." NUBD_MSG="The location of this package is not the default. Be \ sure to update any applicable search paths with the actual \ location of the binaries which are at $WRKNG_BASE/EZstuf \ and $WRKNG_BASE/HRDstuf." Changed="" Suffix="0" # # Determine if this product is actually installed in the working # base directory. # Product_is_present () { if [ -d $WRKNG_BASE/EZstuf -o -d $WRKNG_BASE/HRDstuf ]; then return 1 else return 0 fi } if [ ${BASEDIR} ]; then # This may be an old version of Solaris. In the latest Solaris # CLIENT_BASEDIR won't be defined yet. In older versions it is. if [ ${CLIENT_BASEDIR} ]; then LOCAL_BASE=$BASEDIR/$SUBBASE else # The base directory hasn't been processed yet LOCAL_BASE=${PKG_INSTALL_ROOT}$BASEDIR/$SUBBASE fi WRKNG_BASE=$LOCAL_BASE # See if the base directory is already in place and walk it if # possible while [ -d ${WRKNG_BASE} -a Product_is_present ]; do # There is a conflict # Is this an update of the same arch & version? if [ ${UPDATE} ]; then exit 0 # It's out of our hands. else # So this is a different architecture or # version than what is already there. # Walk the base directory Suffix=`expr $Suffix + 1` WRKNG_BASE=$LOCAL_BASE.$Suffix Changed="true" fi done # So now we can propose a base directory that isn't claimed by # any of our other versions. if [ $Changed ]; then puttext "$MSG" result=`ckyorn -Q -d "a" -h "$HELP" -p "$PROMPT"` if [ $? -eq 3 ]; then puttext "$SUSPEND" exit 1 fi if [ $result="n" ]; then WRKNG_BASE=`ckpath -lyw -d "$WRKNG_BASE" -h "$DIRHELP" \ -p "$DIRPROMPT"` elif [ $result="a" ]; then exit 0 else exit 1 fi echo SUBBASE=$SUBBASE.$Suffix >> $1 puttext "$NUBD_MSG" fi fi exit 0 |
System V 打包背后的原始概念假定每个系统有一个体系结构。服务器的概念在设计中没有发挥作用。当然,现在一台服务器可以支持多个体系结构,这意味着在一台服务器上可能存在同一个软件的多个副本,每个副本适用于不同的体系结构。尽管 Solaris 软件包被隔离在建议的文件系统边界(例如,/ 和 /usr)内,但由于在服务器以及每个客户机上都有产品数据库,因此并非所有安装都一定支持这种划分。某些实现支持完全不同的结构,并且隐含一个公共产品数据库。尽管将客户机指向不同的版本是一项非常简单的工作,但将 System V 软件包实际安装到不同的基目录可能给管理员带来困难。
当您设计软件包时,还应该考虑管理员用来引入新的软件版本的常用方法。管理员通常寻求并行安装和测试最新版本与当前安装的版本。该过程涉及到将新版本安装到与当前版本不同的基目录,以及将一些非关键性客户机定向到新版本以便进行测试。随着信心不断增加,管理员将越来越多的客户机重定向到新版本。最终,管理员仅仅为了应对紧急情况而保留旧版本,并最终将其删除。
这意味着以现代异构系统为目标的软件包必须支持真正的重定位:即管理员可以将这些软件包放置到文件系统中的任意合理位置,而仍然可以使用其完整的功能。Solaris 2.5 和兼容发行版提供了许多有用的工具,允许将多个体系结构和版本完全安装到同一个系统中。Solaris 2.4 和兼容发行版也支持真正的重定位,但完成该任务的方法不是很明显。
System V ABI 表明,可重定位软件包背后的原始意图是使安装软件包对于管理员而言更加便利。现在,对可重定位软件包的需求更进了一大步。便利与否不是唯一的问题,更有可能发生的问题是在安装期间,一个活动的软件产品已经安装在缺省目录中。不能处理这种情况的软件包会覆写现有产品,或者安装失败。然而,可处理多个体系结构和多个版本的软件包可以顺利安装,并且为管理员提供与现有管理传统完全兼容的大量选项。
在某些方面,多个体系结构的问题和多个版本的问题是同一问题。必须可以并行安装现有软件包的变体与其他变体,并且能够在不影响功能的情况下将客户机或导出的文件系统的独立使用者定向到其中任一变体。尽管 Sun 已经制定了在服务器上处理多个体系结构的方法,但管理员可以不遵守这些推荐方法。所有软件包都必须能够符合管理员的合理安装期望。
此示例演示一个传统的可重定位软件包内容。该软件包将位于 /opt/SUNWstuf 中,其 pkginfo 文件和 pkgmap 文件可能如下所示。
# pkginfo file PKG=SUNWstuf NAME=software stuff ARCH=sparc VERSION=1.0.0,REV=1.0.5 CATEGORY=application DESC=a set of utilities that do stuff BASEDIR=/opt VENDOR=Sun Microsystems, Inc. HOTLINE=Please contact your local service provider EMAIL= MAXINST=1000 CLASSES=none PSTAMP=hubert990707141632 |
: 1 1758 1 d none SUNWstuf 0775 root bin 1 d none SUNWstuf/EZstuf 0775 root bin 1 f none SUNWstuf/EZstuf/dirdel 0555 bin bin 40 773 751310229 1 f none SUNWstuf/EZstuf/usrdel 0555 bin bin 40 773 751310229 1 f none SUNWstuf/EZstuf/filedel 0555 bin bin 40 773 751310229 1 d none SUNWstuf/HRDstuf 0775 root bin 1 f none SUNWstuf/HRDstuf/mksmart 0555 bin bin 40 773 751310229 1 f none SUNWstuf/HRDstuf/mktall 0555 bin bin 40 773 751310229 1 f none SUNWstuf/HRDstuf/mkcute 0555 bin bin 40 773 751310229 1 f none SUNWstuf/HRDstuf/mkeasy 0555 bin bin 40 773 751310229 1 i pkginfo 348 28411 760740163 1 i postinstall 323 26475 751309908 1 i postremove 402 33179 751309945 1 i preinstall 321 26254 751310019 1 i preremove 320 26114 751309865 |
因为每个软件包对象都会安装到 pkginfo 文件中的 BASEDIR 参数所定义的基目录,所以这称为传统方法。例如,pkgmap 文件中的第一个对象被安装到目录 /opt/SUNWstuf。
绝对软件包是安装到特定根 (/) 文件系统的软件包。这些软件包难以从多个版本和体系结构的角度进行处理。通常,所有软件包都应该是可重定位的。然而,有非常充足的理由在可重定位软件包中包括绝对元素。
如果 SUNWstuf 软件包是绝对软件包,则不应该在 pkginfo 文件中定义 BASEDIR 参数,而 pkgmap 文件将如下所示。
: 1 1758 1 d none /opt ? ? ? 1 d none /opt/SUNWstuf 0775 root bin 1 d none /opt/SUNWstuf/EZstuf 0775 root bin 1 f none /opt/SUNWstuf/EZstuf/dirdel 0555 bin bin 40 773 751310229 1 f none /opt/SUNWstuf/EZstuf/usrdel 0555 bin bin 40 773 751310229 1 f none /opt/SUNWstuf/EZstuf/filedel 0555 bin bin 40 773 751310229 1 d none /opt/SUNWstuf/HRDstuf 0775 root bin 1 f none /opt/SUNWstuf/HRDstuf/mksmart 0555 bin bin 40 773 751310229 1 f none /opt/SUNWstuf/HRDstuf/mktall 0555 bin bin 40 773 751310229 1 f none /opt/SUNWstuf/HRDstuf/mkcute 0555 bin bin 40 773 751310229 1 f none /opt/SUNWstuf/HRDstuf/mkeasy 0555 bin bin 40 773 751310229 1 i pkginfo 348 28411 760740163 1 i postinstall 323 26475 751309908 1 i postremove 402 33179 751309945 1 i preinstall 321 26254 751310019 1 i preremove 320 26114 751309865 |
在此示例中,如果管理员在安装期间指定了一个备用基目录,pkgadd 命令将忽略该目录。此软件包总是安装到目标系统的 /opt/SUNWstuf。
pkgadd 命令的 -R 参数按预期方式工作。例如,
pkgadd -d . -R /export/opt/client3 SUNWstuf |
将对象安装在 /export/opt/client3/opt/SUNWstuf 中;但这是此软件包最接近可重定位软件包之处。
请注意,在 pkgmap 文件中,对 /opt 目录使用了问号 (?)。这表明不应该更改现有属性。这并不意味着“使用缺省属性创建目录”,尽管在某些情况下可能发生这种情况。特定于新软件包的任何目录都必须明确指定所有属性。
任何包含可重定位对象的软件包称为可重定位软件包。这可能令人产生误解,因为可重定位软件包可能在其 pkgmap 文件中包含绝对路径。在 pkgmap 文件中使用根 (/) 条目可以增强软件包的可重定位特性。同时具有可重定位条目和根条目的软件包称为复合软件包。
假定 SUNWstuf 软件包中的一个对象是在运行级别 2 执行的启动脚本。文件 /etc/rc2.d/S70dostuf 需要作为该软件包的一部分安装,但不能将其放置到基目录中。假定可重定位软件包是唯一的解决方案,pkginfo 和 pkgmap 可能如下所示。
# pkginfo file PKG=SUNWstuf NAME=software stuff ARCH=sparc VERSION=1.0.0,REV=1.0.5 CATEGORY=application DESC=a set of utilities that do stuff BASEDIR=/ VENDOR=Sun Microsystems, Inc. HOTLINE=Please contact your local service provider EMAIL= MAXINST=1000 CLASSES=none PSTAMP=hubert990707141632 |
: 1 1758 1 d none opt/SUNWstuf/EZstuf 0775 root bin 1 f none opt/SUNWstuf/EZstuf/dirdel 0555 bin bin 40 773 751310229 1 f none opt/SUNWstuf/EZstuf/usrdel 0555 bin bin 40 773 751310229 1 f none opt/SUNWstuf/EZstuf/filedel 0555 bin bin 40 773 751310229 1 d none opt/SUNWstuf/HRDstuf 0775 root bin 1 f none opt/SUNWstuf/HRDstuf/mksmart 0555 bin bin 40 773 751310229 1 f none opt/SUNWstuf/HRDstuf/mktall 0555 bin bin 40 773 751310229 1 f none opt/SUNWstuf/HRDstuf/mkcute 0555 bin bin 40 773 751310229 1 f none opt/SUNWstuf/HRDstuf/mkeasy 0555 bin bin 40 773 751310229 1 d none etc ? ? ? 1 d none etc/rc2.d ? ? ? 1 f none etc/rc2.d/S70dostuf 0744 root sys 450 223443 1 i pkginfo 348 28411 760740163 1 i postinstall 323 26475 751309908 1 i postremove 402 33179 751309945 1 i preinstall 321 26254 751310019 1 i preremove 320 26114 751309865 |
此方法和绝对软件包方法之间没有太大差异。实际上,作为绝对软件包使用会更好,因为如果管理员为此软件包提供备用基目录,它将无法正常工作!
实际上,此软件包中只有一个文件需要相对于根保持固定,其余文件都可以任意移动。本节其余部分将讨论如何使用复合软件包解决此问题。
本节描述的方法不适用于所有软件包,但是,在将软件包安装到异构环境期间该方法确实能够改善性能。该方法很少适用于作为 Solaris OS 的一部分提供的软件包(随附的软件包);然而,非随附软件包可以实行非传统打包。
鼓励采用可重定位软件包的原因是支持以下需求:
在添加或删除软件包时,已安装的软件产品的现有理想行为将保持不变。
非随附软件包应该驻留在 /opt 下,以便保证新软件包不会干扰现有产品。
根据软件包对象的绝大部分的安装位置来建立基目录。
如果某个软件包对象安装到基目录以外的其他公共目录(例如 /etc),请在 prototype 文件中将其指定为绝对路径名。
换句话说,因为“可重定位”意味着该对象可以安装到任何位置而仍然能够正常工作,所以不能将 init 在引导时运行的任何启动脚本视为可重定位的!尽管在提供的软件包中将 /etc/passwd 指定为相对路径没有任何问题,但它只有一个安装位置。
如果您要构建一个复合软件包,绝对路径必须以不干扰已安装的现有软件的方式工作。可以完全包含在 /opt 中的软件包可避免此问题,因为不存在形成阻碍的现有文件。当 /etc 中的一个文件包括在软件包中时,您必须确保绝对路径名的行为方式与相对路径名的预期行为方式相同。请考虑下面两个示例。
将一个条目添加到表中,或者对象是可能要由其他程序或软件包修改的一个新表。
将该对象定义为文件类型 e 并定义为属于 build、awk 或 sed 类。执行此任务的脚本必须可与添加自身一样高效地删除自身。
需要将一个条目添加到 /etc/vfstab 中,以便支持新的固态硬盘。
pkgmap 文件中的条目可能是
1 e sed /etc/vfstab ? ? ? |
request 脚本询问操作员 /etc/vfstab 是否应该由软件包修改。如果操作员回答“否”,那么 request 脚本将列出有关如何手动完成该工作的说明,并将执行:
echo "CLASSES=none" >> $1 |
如果操作员回答“是”,那么它将执行:
echo "CLASSES=none sed" >> $1 |
该命令将激活将要执行必要修改的类操作脚本。sed 类意味着软件包文件 /etc/vfstab 是一个 sed 程序,该程序包含目标系统上同名文件的安装和删除操作。
对象是一个不太可能在以后进行编辑的全新文件,或者它将替换另一个软件包拥有的某个文件。
将软件包对象定义为文件类型 f,并使用能够撤消更改的类操作脚本来安装该对象。
/etc 中需要一个全新的文件来提供必要的信息,以便支持名为 /etc/shdisk.conf 的固态硬盘。pkgmap 文件中的条目可能如下所示:
. . . 1 f newetc /etc/shdisk.conf . . . |
类操作脚本 i.newetc 负责安装此文件以及需要安装到 /etc 的所有其他文件。该脚本将执行检查以确保该位置不存在其他文件。如果不存在其他文件,它只是将新文件复制到该位置。如果该位置已经有文件,该脚本将在安装新文件之前备份该文件。脚本 r.newetc 可根据需要删除这些文件并恢复原始文件。以下是安装脚本的关键片段。
# i.newetc while read src dst; do if [ -f $dst ]; then dstfile=`basename $dst` cp $dst $PKGSAV/$dstfile fi cp $src $dst done if [ "${1}" = "ENDOFCLASS" ]; then cd $PKGSAV tar cf SAVE.newetc . $INST_DATADIR/$PKG/install/squish SAVE.newetc fi |
请注意,此脚本使用 PKGSAV 环境变量存储将被替换的文件的备份。当参数 ENDOFCLASS 传递给该脚本时,即 pkgadd 命令通知该脚本这些条目是此类中的最后一批条目,此时,该脚本将归档并压缩使用专用压缩程序(存储在软件包的安装目录中)保存的文件。
尽管在软件包更新期间使用 PKGSAV 环境变量不可靠,但如果软件包未更新(例如,未通过修补程序更新),则备份文件将是安全的。下面的删除脚本包含的代码用于处理另一个问题:pkgrm 命令的旧版本不向脚本传递 PKGSAV 环境变量的正确路径。
删除脚本可能如下所示。
# r.newetc # make sure we have the correct PKGSAV if [ -d $PKG_INSTALL_ROOT$PKGSAV ]; then PKGSAV="$PKG_INSTALL_ROOT$PKGSAV" fi # find the unsquish program UNSQUISH_CMD=`dirname $0`/unsquish while read file; do rm $file done if [ "${1}" = ENDOFCLASS ]; then if [ -f $PKGSAV/SAVE.newetc.sq ]; then $UNSQUISH_CMD $PKGSAV/SAVE.newetc fi if [ -f $PKGSAV/SAVE.newetc ]; then targetdir=dirname $file # get the right directory cd $targetdir tar xf $PKGSAV/SAVE.newetc rm $PKGSAV/SAVE.newetc fi fi |
此脚本使用软件包数据库的安装目录中的专用卸载算法 (unsquish)。这由 pkgadd 命令在安装时自动完成。所有未被 pkgadd 命令特别识别为仅限于安装的脚本都将保留在此目录中,供 pkgrm 命令使用。您无法肯定该目录位于何处,但您可以确定该目录是无层次的,并且包含该软件包的所有适当信息文件和安装脚本。此脚本根据以下事实查找该目录:即类操作脚本肯定从包含 unsquish 程序的目录中执行。
另外请注意,此脚本并不只是假定目标目录是 /etc。该目录实际上可能是 /export/root/client2/etc。可以用以下两种方式之一构建正确的目录。
使用 ${PKG_INSTALL_ROOT}/etc 构造,或者。
获取由 pkgadd 命令传递的文件目录名(就是此脚本的作用)。
通过对软件包中的每个绝对对象使用此方法,您可以确信当前的理想行为保持不变或者至少是可恢复的。
以下是复合软件包的 pkginfo 和 pkgmap 文件的示例。
PKG=SUNWstuf NAME=software stuff ARCH=sparc VERSION=1.0.0,REV=1.0.5 CATEGORY=application DESC=a set of utilities that do stuff BASEDIR=/opt VENDOR=Sun Microsystems, Inc. HOTLINE=Please contact your local service provider EMAIL= MAXINST=1000 CLASSES=none daemon PSTAMP=hubert990707141632 |
: 1 1758 1 d none SUNWstuf/EZstuf 0775 root bin 1 f none SUNWstuf/EZstuf/dirdel 0555 bin bin 40 773 751310229 1 f none SUNWstuf/EZstuf/usrdel 0555 bin bin 40 773 751310229 1 f none SUNWstuf/EZstuf/filedel 0555 bin bin 40 773 751310229 1 d none SUNWstuf/HRDstuf 0775 root bin 1 f none SUNWstuf/HRDstuf/mksmart 0555 bin bin 40 773 751310229 1 f none SUNWstuf/HRDstuf/mktall 0555 bin bin 40 773 751310229 1 f none SUNWstuf/HRDstuf/mkcute 0555 bin bin 40 773 751310229 1 f none SUNWstuf/HRDstuf/mkeasy 0555 bin bin 40 773 751310229 1 d none /etc ? ? ? 1 d none /etc/rc2.d ? ? ? 1 e daemon /etc/rc2.d/S70dostuf 0744 root sys 450 223443 1 i i.daemon 509 39560 752978103 1 i pkginfo 348 28411 760740163 1 i postinstall 323 26475 751309908 1 i postremove 402 33179 751309945 1 i preinstall 321 26254 751310019 1 i preremove 320 26114 751309865 1 i r.daemon 320 24573 742152591 |
尽管 S70dostuf 属于 daemon 类,但指向它的目录(这些目录在安装时已经就位)属于 none 类。即使这些目录对于此软件包是唯一的,您也应该将它们保留在 none 类中。这样做的原因是,这些目录需要首先被创建而且最后删除,而对于 none 类而言始终如此。pkgadd 命令创建这些目录;它们不是从软件包复制的,而且不会传递给将创建的类操作脚本。相反,这些目录由 pkgadd 命令在调用安装类操作脚本之前创建,而 pkgrm 命令会在删除类操作脚本完成之后删除这些目录。
这意味着,如果一个特殊类中的目录包含 none 类中的对象,pkgrm 命令在尝试删除该目录时会失败,因为该目录无法及时变成空目录。如果类 none 的一个对象将插入到某个特殊类的目录中,该目录不会及时出现以便接受该对象。pkgadd 命令将在对象安装期间动态创建该目录,并且可能无法在最终看到 pkgmap 定义时同步该目录的属性。
在将目录指定给类时,请一定记住创建和删除顺序。
所有软件包都必须可远程安装。可远程安装意味着您不假定安装软件包的管理员正在向运行 pkgadd 命令的系统的根 (/) 文件系统进行安装。如果在某个过程脚本中,需要到达目标系统的 /etc/vfstab 文件,则需要使用 PKG_INSTALL_ROOT 环境变量。换句话说,路径名 /etc/vfstab 会将您引导至运行 pkgadd 命令的系统的 /etc/vfstab 文件,但管理员可能正在向位于 /export/root/client3 的客户机进行安装。路径 ${PKG_INSTALL_ROOT}/etc/vfstab 肯定能够将您引导至目标文件系统。
在此示例中,SUNWstuf 软件包被安装到 client3,该客户机在其根 (/) 文件系统中配置有 /opt 。此软件包的另一个版本已经安装在 client3 上,并且基目录从管理文件 thisadmin 中设置为 basedir=/opt/$PKGINST。(有关管理文件的更多信息,请参见缺省管理文件。)服务器上执行的 pkgadd 命令是:
# pkgadd -a thisadmin -R /export/root/client3 SUNWstuf |
下表列出传递给过程脚本的环境变量及其值。
表 6–1 传递给过程脚本的值
环境变量 |
值 |
---|---|
PKGINST |
SUNWstuf.2 |
PKG_INSTALL_ROOT |
/export/root/client3 |
CLIENT_BASEDIR |
/opt/SUNWstuf.2 |
BASEDIR |
/export/root/client3/opt/SUNWstuf.2 |
要在与上一个示例相同的环境下安装到服务器或独立系统,所使用的命令是:
# pkgadd -a thisadmin SUNWstuf |
下表列出传递给过程脚本的环境变量及其值。
表 6–2 传递给过程脚本的值
环境变量 |
值 |
---|---|
PKGINST |
SUNWstuf.2 |
PKG_INSTALL_ROOT |
未定义。 |
CLIENT_BASEDIR |
/opt/SUNWstuf.2 |
BASEDIR |
/opt/SUNWstuf.2 |
假定 SUNWstuf 软件包在服务器上的 /export/SUNWstuf/share 创建并共享了一个文件系统。在将软件包安装到客户机系统时,需要更新客户机系统的 /etc/vfstab 文件以挂载这一共享文件系统。在这种情况下可以使用 CLIENT_BASEDIR 变量。
客户机上的条目需要根据客户机的文件系统呈现挂载点。无论安装是从服务器进行还是从客户机进行,都应该正确构建该行。假定服务器的系统名是 $SERVER。您可以转至 $PKG_INSTALL_ROOT/etc/vfstab,并使用 sed 或 awk 命令为客户机的 /etc/vfstab 文件构建以下行。
$SERVER:/export/SUNWstuf/share - $CLIENT_BASEDIR/usr nfs - yes ro |
例如,对于服务器 universe 和客户机系统 client9,客户机系统的 /etc/vfstab 文件中的该行可能如下所示:
universe:/export/SUNWstuf/share - /opt/SUNWstuf.2/usr nfs - yes ro |
正确使用这些参数时,该条目始终挂载客户机的文件系统,而无论它是在本地构建还是从服务器构建。
软件包的修补程序只是一个专门用于覆写原始软件包中某些文件的稀疏软件包。除了在交付介质上节省空间以外,发布稀疏软件包没有其他真正原因。您还可以发布更改了几个文件的完整原始软件包,或者提供通过网络访问经过修改的软件包的权限。只要只有这些新文件实际上是不同的(其他文件未重新编译),pkgadd 命令就会安装这些区别。请查看以下有关修补软件包的准则。
如果系统足够复杂,那么明智的做法是建立一种修补程序标识系统,以便确保任意两个修补程序在试图更正不同的异常行为时不会替换同一个文件。例如,Sun 修补程序基本号被指定它们所负责的文件互斥集。
能够回退修补程序是必要的。
至关重要的一点是,修补程序软件包的版本号必须与原始软件包的版本号相同。您应该使用如下形式的单独 pkginfo 文件条目来跟踪软件包的修补程序状态:
PATCH=patch_number |
如果软件包版本在进行修补后发生改变,则相当于您创建了该软件包的另一个实例,这样,管理修补后的产品将变得极为困难。这种渐进式实例修补方法可以将早期 Solaris OS 发行版的某些优势传递下去,但却使更复杂系统的管理变得繁琐。
修补程序中的所有区域参数都必须与软件包中的区域参数相匹配。
对于组成 Solaris OS 的软件包而言,虽然可能有多个修补后的实例,但在软件包数据库中应该只有该软件包的一个副本。为了使用 removef 命令从已安装的软件包中删除某个对象,您需要判断哪些实例拥有该文件。
然而,如果您的软件包(该软件包不属于 Solaris OS)需要确定属于 Solaris OS 的特定软件包的修补级别,那么这就成为一个需要在此处解决的问题。安装脚本可能非常大但不会产生重大影向,因为它们不存储在目标文件系统中。通过类操作脚本和其他各种过程脚本,您可以使用 PKGSAV 环境变量保存经过更改的文件(或者保存到其他某个更加持久的目录),以便能够回退已安装的修补程序。您还可以通过 request 脚本设置适当的环境变量,以监视修补程序历史记录。以下各节中的脚本假定可能存在多个修补程序,其编号方案在应用于单个软件包时具有某种含义。在这种情况下,各个修补程序编号表示软件包内的功能相关文件的一个子集。两个编号不同的修补程序不能更改同一个文件。
为了将一个常规的稀疏软件包转换成修补程序软件包,可将以下各节描述的脚本简单地封装到该软件包中。这些脚本都是公认的标准软件包组件,只有最后两个名为 patch_checkinstall 和 patch_postinstall 的脚本除外。如果您要包括回退修补程序功能,可以将这两个脚本合并到回退软件包中。这些脚本十分简单,它们的各种任务也容易完成。
此修补方法可用于修补客户机系统,但服务器上的客户机根目录必须拥有正确的权限,以允许用户 install 或 nobody 读取。
checkinstall 脚本验证修补程序是否适合于此特定软件包。一旦确认这一点,该脚本将构建修补程序列表和修补程序信息列表,然后将它们插入到响应文件中,以便合并到软件包数据库中。
修补程序列表是已经影响当前软件包的修补程序的列表。这一修补程序列表记录在已安装的软件包的 pkginfo 文件中,记录该列表的行可能如下所示:
PATCHLIST=patch_id patch_id ... |
修补程序信息列表是当前修补程序所依赖的修补程序的列表。这一修补程序列表也记录在 pkginfo 文件中,记录该列表的行可能如下所示。
PATCH_INFO_103203-01=Installed... Obsoletes:103201-01 Requires: \ Incompatibles: 120134-01 |
这些行(及其格式)被声明为公共接口。任何为 Solaris 软件包发行修补程序的公司都应该相应地更新此列表。当修补程序交付时,修补程序内的每个软件包都包含一个执行此任务的 checkinstall 脚本。该同一个 checkinstall 脚本还会更新其他一些特定于修补程序的参数。这是新的修补程序体系结构,称为“直接实例修补”。
在此示例中,原始软件包及其修补程序都存在于同一个目录中。两个原始软件包命名为 SUNWstuf.v1 和 SUNWstuf.v2,而它们的修补程序分别命名为 SUNWstuf.p1 和 SUNWstuf.p2。这意味着,过程脚本可能很难判断这些文件来自哪个目录,因为对于 PKG 参数除去了软件包名称中的点 (".") 之后的所有内容,并且 PKGINST 环境变量是指已安装的实例,而不是源实例。因此,过程脚本可以找到源目录,checkinstall 脚本(始终从源目录执行)执行查询并且将位置作为变量 SCRIPTS_DIR 传递。如果源目录中只有一个名为 SUNWstuf 的软件包,则过程脚本可能已经使用 $INSTDIR/$PKG 找到它。
# checkinstall script to control a patch installation. # directory format options. # # @(#)checkinstall 1.6 96/09/27 SMI # # Copyright (c) 1995 by Sun Microsystems, Inc. # All rights reserved # PATH=/usr/sadm/bin:$PATH INFO_DIR=`dirname $0` INFO_DIR=`dirname $INFO_DIR` # one level up NOVERS_MSG="PaTcH_MsG 8 Version $VERSION of $PKG is not installed on this system." ALRDY_MSG="PaTcH_MsG 2 Patch number $Patch_label is already applied." TEMP_MSG="PaTcH_MsG 23 Patch number $Patch_label cannot be applied until all \ restricted patches are backed out." # Read the provided environment from what may have been a request script . $1 # Old systems can't deal with checkinstall scripts anyway if [ "$PATCH_PROGRESSIVE" = "true" ]; then exit 0 fi # # Confirm that the intended version is installed on the system. # if [ "${UPDATE}" != "yes" ]; then echo "$NOVERS_MSG" exit 3 fi # # Confirm that this patch hasn't already been applied and # that no other mix-ups have occurred involving patch versions and # the like. # Skip=0 active_base=`echo $Patch_label | nawk ' { print substr($0, 1, match($0, "Patchvers_pfx")-1) } '` active_inst=`echo $Patch_label | nawk ' { print substr($0, match($0, "Patchvers_pfx")+Patchvers_pfx_lnth) } '` # Is this a restricted patch? if echo $active_base | egrep -s "Patchstrict_str"; then is_restricted="true" # All restricted patches are backoutable echo "PATCH_NO_UNDO=" >> $1 else is_restricted="false" fi for patchappl in ${PATCHLIST}; do # Is this an ordinary patch applying over a restricted patch? if [ $is_restricted = "false" ]; then if echo $patchappl | egrep -s "Patchstrict_str"; then echo "$TEMP_MSG" exit 3; fi fi # Is there a newer version of this patch? appl_base=`echo $patchappl | nawk ' { print substr($0, 1, match($0, "Patchvers_pfx")-1) } '` if [ $appl_base = $active_base ]; then appl_inst=`echo $patchappl | nawk ' { print substr($0, match($0, "Patchvers_pfx")\ +Patchvers_pfx_lnth) } '` result=`expr $appl_inst \> $active_inst` if [ $result -eq 1 ]; then echo "PaTcH_MsG 1 Patch number $Patch_label is \ superceded by the already applied $patchappl." exit 3 elif [ $appl_inst = $active_inst ]; then # Not newer, it's the same if [ "$PATCH_UNCONDITIONAL" = "true" ]; then if [ -d $PKGSAV/$Patch_label ]; then echo "PATCH_NO_UNDO=true" >> $1 fi else echo "$ALRDY_MSG" exit 3; fi fi fi done # Construct a list of applied patches in order echo "PATCHLIST=${PATCHLIST} $Patch_label" >> $1 # # Construct the complete list of patches this one obsoletes # ACTIVE_OBSOLETES=$Obsoletes_label if [ -n "$Obsoletes_label" ]; then # Merge the two lists echo $Obsoletes_label | sed 'y/\ /\n/' | \ nawk -v PatchObsList="$PATCH_OBSOLETES" ' BEGIN { printf("PATCH_OBSOLETES="); PatchCount=split(PatchObsList, PatchObsComp, " "); for(PatchIndex in PatchObsComp) { Atisat=match(PatchObsComp[PatchIndex], "@"); PatchObs[PatchIndex]=substr(PatchObsComp[PatchIndex], \ 0, Atisat-1); PatchObsCnt[PatchIndex]=substr(PatchObsComp\ [PatchIndex], Atisat+1); } } { Inserted=0; for(PatchIndex in PatchObs) { if (PatchObs[PatchIndex] == $0) { if (Inserted == 0) { PatchObsCnt[PatchIndex]=PatchObsCnt\ [PatchIndex]+1; Inserted=1; } else { PatchObsCnt[PatchIndex]=0; } } } if (Inserted == 0) { printf ("%s@1 ", $0); } next; } END { for(PatchIndex in PatchObs) { if ( PatchObsCnt[PatchIndex] != 0) { printf("%s@%d ", PatchObs[PatchIndex], \ PatchObsCnt[PatchIndex]); } } printf("\n"); } ' >> $1 # Clear the parameter since it has already been used. echo "Obsoletes_label=" >> $1 # Pass it's value on to the preinstall under another name echo "ACTIVE_OBSOLETES=$ACTIVE_OBSOLETES" >> $1 fi # # Construct PATCH_INFO line for this package. # tmpRequire=`nawk -F= ' $1 ~ /REQUIR/ { print $2 } ' $INFO_DIR/pkginfo ` tmpIncompat=`nawk -F= ' $1 ~ /INCOMPAT/ { print $2 } ' $INFO_DIR/pkginfo ` if [ -n "$tmpRequire" ] && [ -n "$tmpIncompat" ] then echo "PATCH_INFO_$Patch_label=Installed: `date` From: `uname -n` \ Obsoletes: $ACTIVE_OBSOLETES Requires: $tmpRequire \ Incompatibles: $tmpIncompat" >> $1 elif [ -n "$tmpRequire" ] then echo "PATCH_INFO_$Patch_label=Installed: `date` From: `uname -n` \ Obsoletes: $ACTIVE_OBSOLETES Requires: $tmpRequire \ Incompatibles: " >> $1 elif [ -n "$tmpIncompat" ] then echo "PATCH_INFO_$Patch_label=Installed: `date` From: `uname -n` \ Obsoletes: $ACTIVE_OBSOLETES Requires: Incompatibles: \ $tmpIncompat" >> $1 else echo "PATCH_INFO_$Patch_label=Installed: `date` From: `uname -n` \ Obsoletes: $ACTIVE_OBSOLETES Requires: Incompatibles: " >> $1 fi # # Since this script is called from the delivery medium and we may be using # dot extensions to distinguish the different patch packages, this is the # only place we can, with certainty, trace that source for our backout # scripts. (Usually $INST_DATADIR would get us there). # echo "SCRIPTS_DIR=`dirname $0`" >> $1 # If additional operations are required for this package, place # those package-specific commands here. #XXXSpecial_CommandsXXX# exit 0 |
preinstall 脚本可初始化 prototype 文件、信息文件和安装脚本,以便构建回退软件包。此脚本非常简单,并且此示例中的其余脚本只允许回退软件包描述常规文件。
如果您要在回退软件包中恢复符号链接、硬链接、设备和命名管道,可以修改 preinstall 脚本,以便使用 pkgproto 命令将交付的 pkgmap 文件与安装的文件进行比较,然后为要在回退软件包中更改的每个非文件创建一个 prototype 文件条目。应该使用的方法类似于类操作脚本中的方法。
脚本 patch_checkinstall 和 patch_postinstall 会从 preinstall 脚本插入到软件包源树中。这两个脚本撤消了修补程序执行的操作。
# This script initializes the backout data for a patch package # directory format options. # # @(#)preinstall 1.5 96/05/10 SMI # # Copyright (c) 1995 by Sun Microsystems, Inc. # All rights reserved # PATH=/usr/sadm/bin:$PATH recovery="no" if [ "$PKG_INSTALL_ROOT" = "/" ]; then PKG_INSTALL_ROOT="" fi # Check to see if this is a patch installation retry. if [ "$INTERRUPTION" = "yes" ]; then if [ -d "$PKG_INSTALL_ROOT/var/tmp/$Patch_label.$PKGINST" ] || [ -d \ "$PATCH_BUILD_DIR/$Patch_label.$PKGINST" ]; then recovery="yes" fi fi if [ -n "$PATCH_BUILD_DIR" -a -d "$PATCH_BUILD_DIR" ]; then BUILD_DIR="$PATCH_BUILD_DIR/$Patch_label.$PKGINST" else BUILD_DIR="$PKG_INSTALL_ROOT/var/tmp/$Patch_label.$PKGINST" fi FILE_DIR=$BUILD_DIR/files RELOC_DIR=$BUILD_DIR/files/reloc ROOT_DIR=$BUILD_DIR/files/root PROTO_FILE=$BUILD_DIR/prototype PKGINFO_FILE=$BUILD_DIR/pkginfo THIS_DIR=`dirname $0` if [ "$PATCH_PROGRESSIVE" = "true" ]; then # If this is being used in an old-style patch, insert # the old-style script commands here. #XXXOld_CommandsXXX# exit 0 fi # # Unless specifically denied, initialize the backout patch data by # creating the build directory and copying over the original pkginfo # which pkgadd saved in case it had to be restored. # if [ "$PATCH_NO_UNDO" != "true" ] && [ "$recovery" = "no" ]; then if [ -d $BUILD_DIR ]; then rm -r $BUILD_DIR fi # If this is a retry of the same patch then recovery is set to # yes. Which means there is a build directory already in # place with the correct backout data. if [ "$recovery" = "no" ]; then mkdir $BUILD_DIR mkdir -p $RELOC_DIR mkdir $ROOT_DIR fi # # Here we initialize the backout pkginfo file by first # copying over the old pkginfo file and themn adding the # ACTIVE_PATCH parameter so the backout will know what patch # it's backing out. # # NOTE : Within the installation, pkgparam returns the # original data. # pkgparam -v $PKGINST | nawk ' $1 ~ /PATCHLIST/ { next; } $1 ~ /PATCH_OBSOLETES/ { next; } $1 ~ /ACTIVE_OBSOLETES/ { next; } $1 ~ /Obsoletes_label/ { next; } $1 ~ /ACTIVE_PATCH/ { next; } $1 ~ /Patch_label/ { next; } $1 ~ /UPDATE/ { next; } $1 ~ /SCRIPTS_DIR/ { next; } $1 ~ /PATCH_NO_UNDO/ { next; } $1 ~ /INSTDATE/ { next; } $1 ~ /PKGINST/ { next; } $1 ~ /OAMBASE/ { next; } $1 ~ /PATH/ { next; } { print; } ' > $PKGINFO_FILE echo "ACTIVE_PATCH=$Patch_label" >> $PKGINFO_FILE echo "ACTIVE_OBSOLETES=$ACTIVE_OBSOLETES" >> $PKGINFO_FILE # And now initialize the backout prototype file with the # pkginfo file just formulated. echo "i pkginfo" > $PROTO_FILE # Copy over the backout scripts including the undo class # action scripts for script in $SCRIPTS_DIR/*; do srcscript=`basename $script` targscript=`echo $srcscript | nawk ' { script=$0; } /u\./ { sub("u.", "i.", script); print script; next; } /patch_/ { sub("patch_", "", script); print script; next; } { print "dont_use" } '` if [ "$targscript" = "dont_use" ]; then continue fi echo "i $targscript=$FILE_DIR/$targscript" >> $PROTO_FILE cp $SCRIPTS_DIR/$srcscript $FILE_DIR/$targscript done # # Now add entries to the prototype file that won't be passed to # class action scripts. If the entry is brand new, add it to the # deletes file for the backout package. # Our_Pkgmap=`dirname $SCRIPTS_DIR`/pkgmap BO_Deletes=$FILE_DIR/deletes nawk -v basedir=${BASEDIR:-/} ' BEGIN { count=0; } { token = $2; ftype = $1; } $1 ~ /[#\!:]/ { next; } $1 ~ /[0123456789]/ { if ( NF >= 3) { token = $3; ftype = $2; } else { next; } } { if (ftype == "i" || ftype == "e" || ftype == "f" || ftype == \ "v" || ftype == "d") { next; } } { equals=match($4, "=")-1; if ( equals == -1 ) { print $3, $4; } else { print $3, substr($4, 0, equals); } } ' < $Our_Pkgmap | while read class path; do # # NOTE: If pkgproto is passed a file that is # actually a hard link to another file, it # will return ftype "f" because the first link # in the list (consisting of only one file) is # viewed by pkgproto as the source and always # gets ftype "f". # # If this isn't replacing something, then it # just goes to the deletes list. # if valpath -l $path; then Chk_Path="$BASEDIR/$path" Build_Path="$RELOC_DIR/$path" Proto_From="$BASEDIR" else # It's an absolute path Chk_Path="$PKG_INSTALL_ROOT$path" Build_Path="$ROOT_DIR$path" Proto_From="$PKG_INSTALL_ROOT" fi # # Hard links have to be restored as regular files. # Unlike the others in this group, an actual # object will be required for the pkgmk. # if [ -f "$Chk_Path" ]; then mkdir -p `dirname $Build_Path` cp $Chk_Path $Build_Path cd $Proto_From pkgproto -c $class "$Build_Path=$path" 1>> \ $PROTO_FILE 2> /dev/null cd $THIS_DIR elif [ -h "$Chk_Path" -o \ -c "$Chk_Path" -o \ -b "$Chk_Path" -o \ -p "$Chk_Path" ]; then pkgproto -c $class "$Chk_Path=$path" 1>> \ $PROTO_FILE 2> /dev/null else echo $path >> $BO_Deletes fi done fi # If additional operations are required for this package, place # those package-specific commands here. #XXXSpecial_CommandsXXX# exit 0 |
类操作脚本创建每个替换现有文件的文件的副本,并为回退软件包向 prototype 文件中添加一个相应的行。这一切都由十分简单的 nawk 脚本完成。类操作脚本接收一个源/目标对列表,其中包括不与相应的已安装文件匹配的普通文件。必须在 preinstall 脚本中处理符号链接和其他非文件。
# This class action script copies the files being replaced # into a package being constructed in $BUILD_DIR. This class # action script is only appropriate for regular files that # are installed by simply copying them into place. # # For special package objects such as editable files, the patch # producer must supply appropriate class action scripts. # # directory format options. # # @(#)i.script 1.6 96/05/10 SMI # # Copyright (c) 1995 by Sun Microsystems, Inc. # All rights reserved # PATH=/usr/sadm/bin:$PATH ECHO="/usr/bin/echo" SED="/usr/bin/sed" PKGPROTO="/usr/bin/pkgproto" EXPR="/usr/bin/expr" # used by dirname MKDIR="/usr/bin/mkdir" CP="/usr/bin/cp" RM="/usr/bin/rm" MV="/usr/bin/mv" recovery="no" Pn=$$ procIdCtr=0 CMDS_USED="$ECHO $SED $PKGPROTO $EXPR $MKDIR $CP $RM $MV" LIBS_USED="" if [ "$PKG_INSTALL_ROOT" = "/" ]; then PKG_INSTALL_ROOT="" fi # Check to see if this is a patch installation retry. if [ "$INTERRUPTION" = "yes" ]; then if [ -d "$PKG_INSTALL_ROOT/var/tmp/$Patch_label.$PKGINST" ] || \ [ -d "$PATCH_BUILD_DIR/$Patch_label.$PKGINST" ]; then recovery="yes" fi fi if [ -n "$PATCH_BUILD_DIR" -a -d "$PATCH_BUILD_DIR" ]; then BUILD_DIR="$PATCH_BUILD_DIR/$Patch_label.$PKGINST" else BUILD_DIR="$PKG_INSTALL_ROOT/var/tmp/$Patch_label.$PKGINST" fi FILE_DIR=$BUILD_DIR/files RELOC_DIR=$FILE_DIR/reloc ROOT_DIR=$FILE_DIR/root BO_Deletes=$FILE_DIR/deletes PROGNAME=`basename $0` if [ "$PATCH_PROGRESSIVE" = "true" ]; then PATCH_NO_UNDO="true" fi # Since this is generic, figure out the class. Class=`echo $PROGNAME | nawk ' { print substr($0, 3) }'` # Since this is an update, $BASEDIR is guaranteed to be correct BD=${BASEDIR:-/} cd $BD # # First, figure out the dynamic libraries that can trip us up. # if [ -z "$PKG_INSTALL_ROOT" ]; then if [ -x /usr/bin/ldd ]; then LIB_LIST=`/usr/bin/ldd $CMDS_USED | sort -u | nawk ' $1 ~ /\// { continue; } { printf "%s ", $3 } '` else LIB_LIST="/usr/lib/libc.so.1 /usr/lib/libdl.so.1 \ /usr/lib/libw.so.1 /usr/lib/libintl.so.1 /usr/lib/libadm.so.1 \ /usr/lib/libelf.so.1" fi fi # # Now read the list of files in this class to be replaced. If the file # is already in place, then this is a change and we need to copy it # over to the build directory if undo is allowed. If it's a new entry # (No $dst), then it goes in the deletes file for the backout package. # procIdCtr=0 while read src dst; do if [ -z "$PKG_INSTALL_ROOT" ]; then Chk_Path=$dst for library in $LIB_LIST; do if [ $Chk_Path = $library ]; then $CP $dst $dst.$Pn LIBS_USED="$LIBS_USED $dst.$Pn" LD_PRELOAD="$LIBS_USED" export LD_PRELOAD fi done fi if [ "$PATCH_PROGRESSIVE" = "true" ]; then # If this is being used in an old-style patch, insert # the old-style script commands here. #XXXOld_CommandsXXX# echo >/dev/null # dummy fi if [ "${PATCH_NO_UNDO}" != "true" ]; then # # Here we construct the path to the appropriate source # tree for the build. First we try to strip BASEDIR. If # there's no BASEDIR in the path, we presume that it is # absolute and construct the target as an absolute path # by stripping PKG_INSTALL_ROOT. FS_Path is the path to # the file on the file system (for deletion purposes). # Build_Path is the path to the object in the build # environment. # if [ "$BD" = "/" ]; then FS_Path=`$ECHO $dst | $SED s@"$BD"@@` else FS_Path=`$ECHO $dst | $SED s@"$BD/"@@` fi # If it's an absolute path the attempt to strip the # BASEDIR will have failed. if [ $dst = $FS_Path ]; then if [ -z "$PKG_INSTALL_ROOT" ]; then FS_Path=$dst Build_Path="$ROOT_DIR$dst" else Build_Path="$ROOT_DIR`echo $dst | \ sed s@"$PKG_INSTALL_ROOT"@@`" FS_Path=`echo $dst | \ sed s@"$PKG_INSTALL_ROOT"@@` fi else Build_Path="$RELOC_DIR/$FS_Path" fi if [ -f $dst ]; then # If this is replacing something cd $FILE_DIR # # Construct the prototype file entry. We replace # the pointer to the filesystem object with the # build directory object. # $PKGPROTO -c $Class $dst=$FS_Path | \ $SED -e s@=$dst@=$Build_Path@ >> \ $BUILD_DIR/prototype # Now copy over the file if [ "$recovery" = "no" ]; then DirName=`dirname $Build_Path` $MKDIR -p $DirName $CP -p $dst $Build_Path else # If this file is already in the build area skip it if [ -f "$Build_Path" ]; then cd $BD continue else DirName=`dirname $Build_Path` if [ ! -d "$DirName" ]; then $MKDIR -p $DirName fi $CP -p $dst $Build_Path fi fi cd $BD else # It's brand new $ECHO $FS_Path >> $BO_Deletes fi fi # If special processing is required for each src/dst pair, # add that here. # #XXXSpecial_CommandsXXX# # $CP $src $dst.$$$procIdCtr if [ $? -ne 0 ]; then $RM $dst.$$$procIdCtr 1>/dev/null 2>&1 else $MV -f $dst.$$$procIdCtr $dst for library in $LIB_LIST; do if [ "$library" = "$dst" ]; then LD_PRELOAD="$dst" export LD_PRELOAD fi done fi procIdCtr=`expr $procIdCtr + 1` done # If additional operations are required for this package, place # those package-specific commands here. #XXXSpecial_CommandsXXX# # # Release the dynamic libraries # for library in $LIBS_USED; do $RM -f $library done exit 0 |
postinstall 脚本使用其他脚本提供的信息创建回退软件包。因为 pkgmk 和 pkgtrans 命令不需要软件包数据库,所以可以在软件包安装时执行这些命令。
在此示例中,通过在保存目录中(使用 PKGSAV 环境变量)构建一个流格式软件包来允许撤消修补程序。这并不显而易见,但此软件包必须采用流格式,因为在 pkgadd 操作期间,保存目录会发生移动。如果 pkgadd 命令应用于自己的保存目录中的一个软件包,那么有关软件包源在任何指定时刻所在位置的假设将变得非常不可靠。流格式软件包被解压缩到一个临时目录中并在此处安装。(目录格式软件包将从保存目录开始安装,并且发现自己在 pkgadd 失败安全操作期间突然被重定位。)
要确定应用于软件包的修补程序,请使用以下命令:
$ pkgparam SUNWstuf PATCHLIST |
除 PATCHLIST(一个 Sun 公共接口)之外,此示例中的参数名称中没有什么重要内容。您可以取代 PATCH 使用传统 SUNW_PATCHID,而其他各种列表(例如 PATCH_EXCL 和 PATCH_REQD)可以相应地重命名。
如果某些修补程序软件包依赖于可从同一个介质中获得的其他修补程序软件包,那么 checkinstall 脚本可以确定这一点,并且按照与升级示例(请参见 升级软件包)相同的方式创建一个将由 postinstall 脚本执行的脚本。
# This script creates the backout package for a patch package # # directory format options. # # @(#) postinstall 1.6 96/01/29 SMI # # Copyright (c) 1995 by Sun Microsystems, Inc. # All rights reserved # # Description: # Set the TYPE parameter for the remote file # # Parameters: # none # # Globals set: # TYPE set_TYPE_parameter () { if [ ${PATCH_UNDO_ARCHIVE:?????} = "/dev" ]; then # handle device specific stuff TYPE="removable" else TYPE="filesystem" fi } # # Description: # Build the remote file that points to the backout data # # Parameters: # $1: the un/compressed undo archive # # Globals set: # UNDO, STATE build_remote_file () { remote_path=$PKGSAV/$Patch_label/remote set_TYPE_parameter STATE="active" if [ $1 = "undo" ]; then UNDO="undo" else UNDO="undo.Z" fi cat > $remote_path << EOF # Backout data stored remotely TYPE=$TYPE FIND_AT=$ARCHIVE_DIR/$UNDO STATE=$STATE EOF } PATH=/usr/sadm/bin:$PATH if [ "$PKG_INSTALL_ROOT" = "/" ]; then PKG_INSTALL_ROOT="" fi if [ -n "$PATCH_BUILD_DIR" -a -d "$PATCH_BUILD_DIR" ]; then BUILD_DIR="$PATCH_BUILD_DIR/$Patch_label.$PKGINST" else BUILD_DIR="$PKG_INSTALL_ROOT/var/tmp/$Patch_label.$PKGINST" fi if [ ! -n "$PATCH_UNDO_ARCHIVE" ]; then PATCH_UNDO_ARCHIVE="none" fi FILE_DIR=$BUILD_DIR/files RELOC_DIR=$FILE_DIR/reloc ROOT_DIR=$FILE_DIR/root BO_Deletes=$FILE_DIR/deletes THIS_DIR=`dirname $0` PROTO_FILE=$BUILD_DIR/prototype TEMP_REMOTE=$PKGSAV/$Patch_label/temp if [ "$PATCH_PROGRESSIVE" = "true" ]; then # remove the scripts that are left behind install_scripts=`dirname $0` rm $install_scripts/checkinstall \ $install_scripts/patch_checkinstall $install_scripts/patch_postinstall # If this is being used in an old-style patch, insert # the old-style script commands here. #XXXOld_CommandsXXX# exit 0 fi # # At this point we either have a deletes file or we don't. If we do, # we create a prototype entry. # if [ -f $BO_Deletes ]; then echo "i deletes=$BO_Deletes" >> $BUILD_DIR/prototype fi # # Now delete everything in the deletes list after transferring # the file to the backout package and the entry to the prototype # file. Remember that the pkgmap will get the CLIENT_BASEDIR path # but we have to actually get at it using the BASEDIR path. Also # remember that removef will import our PKG_INSTALL_ROOT # Our_Deletes=$THIS_DIR/deletes if [ -f $Our_Deletes ]; then cd $BASEDIR cat $Our_Deletes | while read path; do Reg_File=0 if valpath -l $path; then Client_Path="$CLIENT_BASEDIR/$path" Build_Path="$RELOC_DIR/$path" Proto_Path=$BASEDIR/$path else # It's an absolute path Client_Path=$path Build_Path="$ROOT_DIR$path" Proto_Path=$PKG_INSTALL_ROOT$path fi # Note: If the file isn't really there, pkgproto # doesn't write anything. LINE=`pkgproto $Proto_Path=$path` ftype=`echo $LINE | nawk '{ print $1 }'` if [ $ftype = "f" ]; then Reg_File=1 fi if [ $Reg_File = 1 ]; then # Add source file to the prototype entry if [ "$Proto_Path" = "$path" ]; then LINE=`echo $LINE | sed -e s@$Proto_Path@$Build_Path@2` else LINE=`echo $LINE | sed -e s@$Proto_Path@$Build_Path@` fi DirName=`dirname $Build_Path` # make room in the build tree mkdir -p $DirName cp -p $Proto_Path $Build_Path fi # Insert it into the prototype file echo $LINE 1>>$PROTO_FILE 2>/dev/null # Remove the file only if it's OK'd by removef rm `removef $PKGINST $Client_Path` 1>/dev/null 2>&1 done removef -f $PKGINST rm $Our_Deletes fi # # Unless specifically denied, make the backout package. # if [ "$PATCH_NO_UNDO" != "true" ]; then cd $BUILD_DIR # We have to build from here. if [ "$PATCH_UNDO_ARCHIVE" != "none" ]; then STAGE_DIR="$PATCH_UNDO_ARCHIVE" ARCHIVE_DIR="$PATCH_UNDO_ARCHIVE/$Patch_label/$PKGINST" mkdir -p $ARCHIVE_DIR mkdir -p $PKGSAV/$Patch_label else if [ -d $PKGSAV/$Patch_label ]; then rm -r $PKGSAV/$Patch_label fi STAGE_DIR=$PKGSAV ARCHIVE_DIR=$PKGSAV/$Patch_label mkdir $ARCHIVE_DIR fi pkgmk -o -d $STAGE_DIR 1>/dev/null 2>&1 pkgtrans -s $STAGE_DIR $ARCHIVE_DIR/undo $PKG 1>/dev/null 2>&1 compress $ARCHIVE_DIR/undo retcode=$? if [ "$PATCH_UNDO_ARCHIVE" != "none" ]; then if [ $retcode != 0 ]; then build_remote_file "undo" else build_remote_file "undo.Z" fi fi rm -r $STAGE_DIR/$PKG cd .. rm -r $BUILD_DIR # remove the scripts that are left behind install_scripts=`dirname $0` rm $install_scripts/checkinstall $install_scripts/patch_\ checkinstall $install_scripts/patch_postinstall fi # # Since this apparently worked, we'll mark as obsoleted the prior # versions of this patch - installpatch deals with explicit obsoletions. # cd ${PKG_INSTALL_ROOT:-/} cd var/sadm/pkg active_base=`echo $Patch_label | nawk ' { print substr($0, 1, match($0, "Patchvers_pfx")-1) } '` List=`ls -d $PKGINST/save/${active_base}*` if [ $? -ne 0 ]; then List="" fi for savedir in $List; do patch=`basename $savedir` if [ $patch = $Patch_label ]; then break fi # If we get here then the previous patch gets deleted if [ -f $savedir/undo ]; then mv $savedir/undo $savedir/obsolete echo $Patch_label >> $savedir/obsoleted_by elif [ -f $savedir/undo.Z ]; then mv $savedir/undo.Z $savedir/obsolete.Z echo $Patch_label >> $savedir/obsoleted_by elif [ -f $savedir/remote ]; then `grep . $PKGSAV/$patch/remote | sed 's/STATE=.*/STATE=obsolete/ ' > $TEMP_REMOTE` rm -f $PKGSAV/$patch/remote mv $TEMP_REMOTE $PKGSAV/$patch/remote rm -f $TEMP_REMOTE echo $Patch_label >> $savedir/obsoleted_by elif [ -f $savedir/obsolete -o -f $savedir/obsolete.Z ]; then echo $Patch_label >> $savedir/obsoleted_by fi done # If additional operations are required for this package, place # those package-specific commands here. #XXXSpecial_CommandsXXX# exit 0 |
# checkinstall script to validate backing out a patch. # directory format option. # # @(#)patch_checkinstall 1.2 95/10/10 SMI # # Copyright (c) 1995 by Sun Microsystems, Inc. # All rights reserved # PATH=/usr/sadm/bin:$PATH LATER_MSG="PaTcH_MsG 6 ERROR: A later version of this patch is applied." NOPATCH_MSG="PaTcH_MsG 2 ERROR: Patch number $ACTIVE_PATCH is not installed" NEW_LIST="" # Get OLDLIST . $1 # # Confirm that the patch that got us here is the latest one installed on # the system and remove it from PATCHLIST. # Is_Inst=0 Skip=0 active_base=`echo $ACTIVE_PATCH | nawk ' { print substr($0, 1, match($0, "Patchvers_pfx")-1) } '` active_inst=`echo $ACTIVE_PATCH | nawk ' { print substr($0, match($0, "Patchvers_pfx")+1) } '` for patchappl in ${OLDLIST}; do appl_base=`echo $patchappl | nawk ' { print substr($0, 1, match($0, "Patchvers_pfx")-1) } '` if [ $appl_base = $active_base ]; then appl_inst=`echo $patchappl | nawk ' { print substr($0, match($0, "Patchvers_pfx")+1) } '` result=`expr $appl_inst \> $active_inst` if [ $result -eq 1 ]; then puttext "$LATER_MSG" exit 3 elif [ $appl_inst = $active_inst ]; then Is_Inst=1 Skip=1 fi fi if [ $Skip = 1 ]; then Skip=0 else NEW_LIST="${NEW_LIST} $patchappl" fi done if [ $Is_Inst = 0 ]; then puttext "$NOPATCH_MSG" exit 3 fi # # OK, all's well. Now condition the key variables. # echo "PATCHLIST=${NEW_LIST}" >> $1 echo "Patch_label=" >> $1 echo "PATCH_INFO_$ACTIVE_PATCH=backed out" >> $1 # Get the current PATCH_OBSOLETES and condition it Old_Obsoletes=$PATCH_OBSOLETES echo $ACTIVE_OBSOLETES | sed 'y/\ /\n/' | \ nawk -v PatchObsList="$Old_Obsoletes" ' BEGIN { printf("PATCH_OBSOLETES="); PatchCount=split(PatchObsList, PatchObsComp, " "); for(PatchIndex in PatchObsComp) { Atisat=match(PatchObsComp[PatchIndex], "@"); PatchObs[PatchIndex]=substr(PatchObsComp[PatchIndex], \ 0, Atisat-1); PatchObsCnt[PatchIndex]=substr(PatchObsComp\ [PatchIndex], Atisat+1); } } { for(PatchIndex in PatchObs) { if (PatchObs[PatchIndex] == $0) { PatchObsCnt[PatchIndex]=PatchObsCnt[PatchIndex]-1; } } next; } END { for(PatchIndex in PatchObs) { if ( PatchObsCnt[PatchIndex] > 0 ) { printf("%s@%d ", PatchObs[PatchIndex], PatchObsCnt\ [PatchIndex]); } } printf("\n"); } ' >> $1 # remove the used parameters echo "ACTIVE_OBSOLETES=" >> $1 echo "Obsoletes_label=" >> $1 exit 0 |
# This script deletes the used backout data for a patch package # and removes the deletes file entries. # # directory format options. # # @(#)patch_postinstall 1.2 96/01/29 SMI # # Copyright (c) 1995 by Sun Microsystems, Inc. # All rights reserved # PATH=/usr/sadm/bin:$PATH THIS_DIR=`dirname $0` Our_Deletes=$THIS_DIR/deletes # # Delete the used backout data # if [ -f $Our_Deletes ]; then cat $Our_Deletes | while read path; do if valpath -l $path; then Client_Path=`echo "$CLIENT_BASEDIR/$path" | sed s@//@/@` else # It's an absolute path Client_Path=$path fi rm `removef $PKGINST $Client_Path` done removef -f $PKGINST rm $Our_Deletes fi # # Remove the deletes file, checkinstall and the postinstall # rm -r $PKGSAV/$ACTIVE_PATCH rm -f $THIS_DIR/checkinstall $THIS_DIR/postinstall exit 0 |
升级软件包的过程与覆写软件包的过程极为不同。尽管有一些特殊工具支持对作为 Solaris OS 的一部分交付的标准软件包进行升级,但可以设计一个非随附软件包来支持它自己的升级-前面的多个示例描述了一些可在管理员指导下具有前瞻性并且控制精确安装方法的软件包。您还可以设计 request 脚本以支持软件包的直接升级。如果管理员选择安装一个软件包以便完全替换另一个软件包,并且不留下残余的过时文件,软件包脚本可以执行此任务。
此示例中的 request 脚本和 postinstall 脚本提供了一个简单的可升级软件包。request 脚本与管理员通信,然后在 /tmp 目录中设置一个简单的文件以删除旧的软件包实例。(虽然 request 脚本创建了一个被禁止的文件,但这不会有什么问题,因为每个人都有权访问 /tmp。)
然后, postinstall 脚本执行 /tmp 中的 shell 脚本,该脚本对旧软件包执行必要的 pkgrm 命令,然后删除它自身。
此示例演示基本升级。该示例少于 50 行代码,其中包括一些相当长的消息。可以对其进行扩展以回退升级,或者根据设计者的要求对软件包进行其他重要转换。
升级选项的用户界面设计必须完全确保管理员充分了解升级过程,并且已经主动请求升级而不是并行安装。只要用户界面能够清楚地说明操作,执行诸如升级这样的复杂操作就不会出现什么错误。
# request script control an upgrade installation PATH=/usr/sadm/bin:$PATH UPGR_SCRIPT=/tmp/upgr.$PKGINST UPGRADE_MSG="Do you want to upgrade the installed version ?" UPGRADE_HLP="If upgrade is desired, the existing version of the \ package will be replaced by this version. If it is not \ desired, this new version will be installed into a different \ base directory and both versions will be usable." UPGRADE_NOTICE="Conflict approval questions may be displayed. The \ listed files are the ones that will be upgraded. Please \ answer \"y\" to these questions if they are presented." pkginfo -v 1.0 -q SUNWstuf.\* if [ $? -eq 0 ]; then # See if upgrade is desired here response=`ckyorn -p "$UPGRADE_MSG" -h "$UPGRADE_HLP"` if [ $response = "y" ]; then OldPkg=`pkginfo -v 1.0 -x SUNWstuf.\* | nawk ' \ /SUNW/{print $1} '` # Initiate upgrade echo "PATH=/usr/sadm/bin:$PATH" > $UPGR_SCRIPT echo "sleep 3" >> $UPGR_SCRIPT echo "echo Now removing old instance of $PKG" >> \ $UPGR_SCRIPT if [ ${PKG_INSTALL_ROOT} ]; then echo "pkgrm -n -R $PKG_INSTALL_ROOT $OldPkg" >> \ $UPGR_SCRIPT else echo "pkgrm -n $OldPkg" >> $UPGR_SCRIPT fi echo "rm $UPGR_SCRIPT" >> $UPGR_SCRIPT echo "exit $?" >> $UPGR_SCRIPT # Get the original package's base directory OldBD=`pkgparam $OldPkg BASEDIR` echo "BASEDIR=$OldBD" > $1 puttext -l 5 "$UPGRADE_NOTICE" else if [ -f $UPGR_SCRIPT ]; then rm -r $UPGR_SCRIPT fi fi fi exit 0 |
# postinstall to execute a simple upgrade PATH=/usr/sadm/bin:$PATH UPGR_SCRIPT=/tmp/upgr.$PKGINST if [ -f $UPGR_SCRIPT ]; then sh $UPGR_SCRIPT & fi exit 0 |
类归档软件包是对应用称序二进制接口 (Application Binary Interface, ABI) 的增强,其中,某些文件集已经组合为单个文件(即归档文件),并且可能已选择性地压缩或加密。类归档格式最多可使初始安装速度提高 30%,并且在将软件包和修补程序安装到可能活动的文件系统上的过程中提高可靠性。
以下各节提供有关归档软件包目录结构、关键字和 faspac 实用程序的信息。
下图中显示的软件包条目表示包含软件包文件的目录。此目录必须与软件包同名。
下面列出了软件包目录内包含的文件和目录的功能。
项 |
说明 |
---|---|
pkginfo |
文件,对软件包进行总体描述,包括特殊环境变量和安装指令 |
pkgmap |
描述每个要安装对象的文件,如文件、目录或管道 |
reloc |
可选目录,包含要相对于基目录安装的文件(可重定位的对象) |
root |
可选目录,包含要相对于 root 目录安装的文件(根对象) |
install |
可选目录,包含脚本和其他辅助文件(除了 pkginfo 和 pkgmap,所有 ftype i 文件都位于此处) |
使用类归档格式,软件包生成器可以将 reloc 和 root 目录中的文件组合到归档文件中,然后对其进行压缩、加密或以所需的任何方式进行其他处理,以便提高安装速度,减小软件包大小,或者增加软件包的安全性。
ABI 允许将软件包内的任何文件指定给某个类。特定类中的所有文件都可以使用类操作脚本定义的自定义方法安装到磁盘中。此自定义方法可以利用目标系统中提供的程序或随软件包一起提供的程序。得到的格式很像标准 ABI 格式。如下图所示,另一个目录被添加。将要归档的任何文件类只是组合为单个文件,并且放置到 archive 目录中。系统将从 reloc 和 root 目录中删除所有归档文件,并且将一个安装类操作脚本放置到 install 目录中。
为了支持这一新的类归档格式,三个采用关键字形式的新接口在 pkginfo 文件内具有特殊含义。这些关键字用于指定需要特殊处理的类。每个关键字语句的格式为: keyword=class1[class2 ...]。下表定义了每个关键字值。
关键字 |
说明 |
---|---|
PKG_SRC_NOVERIFY |
如果所交付的软件包的 reloc 或 root 目录中的文件属于指定类,则该关键字告诉 pkgadd 不要验证这些文件是否存在以及文件属性。所有归档类都需要此关键字,因为这些文件不再位于 reloc 或 root 目录中。它们是 archive 目录中的专用格式文件。 |
PKG_DST_QKVERIFY |
这些类中的文件在安装后使用一个快速算法进行验证,只有少量或者没有任何文本输出。快速验证首先正确设置每个文件的属性,然后检查以了解该操作是否成功。然后,将根据 pkgmap 测试文件大小和修改时间。不会执行 checksum 验证,该验证与标准验证机制相比错误恢复功能较差。如果在安装期间发生断电或磁盘故障,则目录文件可能与已安装的文件不一致。总能使用 pkgrm 解决这种不一致问题。 |
PKG_CAS_PASSRELATIVE |
通常,安装类操作脚本从 stdin 接收告诉它安装哪些文件的源和目标对列表。指定给 PKG_CAS_PASSRELATIVE 的类不会获得这些源和目标对。相反,这些类会收到单个列表,其中第一个条目是源软件包的位置,其余条目是目标路径。这专门用于简化从归档文件中进行提取的操作。根据源软件包的位置,您可以在 archive 目录中找到归档文件。然后,目标路径被传递给负责提取归档文件内容的函数。所提供的每个目标路径对于基目录而言是绝对的或相对的,具体取决于该路径原来位于 root 还是位于 reloc。如果选择此选项,可能难以将相对路径和绝对路径都组合到单个类中。 |
对于每个归档类,都需要一个类操作脚本。这是一个包含 Bourne shell 命令的文件,该文件由 pkgadd 执行以便从归档中实际安装文件。如果在软件包的 install 目录中找到一个类操作脚本,pkgadd 会将所有安装职责移交给该脚本。该类操作脚本以超级用户权限运行,并且可以将其文件放置在目标系统中的任何位置。
实现类归档软件包绝对必须的唯一关键字是 PKG_SRC_NOVERIFY。其他关键字可用于提高安装速度或保存代码。
faspac 实用程序将标准 ABI 软件包转换为随附软件包所使用的类归档格式。此实用程序使用 cpio 进行归档,使用 compress 进行压缩。生成的软件包在顶层目录中有一个名为 archive 的附加目录。在此目录中,将包含按类命名的所有归档文件。install 目录将包含解压缩每个归档文件所需的类操作脚本。绝对路径将不进行归档。
faspac [-m Archive Method] -a -s -q [-d Base Directory] / [-x Exclude List] [List of Packages] |
下表描述了每个 faspac 命令选项。
选项 |
说明 |
---|---|
-m Archive Method
|
指示一种归档或压缩方法。bzip2 是所使用的缺省压缩实用程序。要切换到 zip 压缩或 unzip 解压缩方法,请使用 -m zip,或者,对于 cpio 或 compress,请使用 -m cpio。 |
-a |
修复属性(只有超级用户才能执行此操作)。 |
-s |
指示标准 ABI 类型软件包转换。此选项将经过 cpio 或压缩处理的软件包进行打包,并使其具有符合标准 ABI 的软件包格式。 |
-q |
指示静默模式。 |
-d Base Directory |
指示所有软件包所在的目录将由命令行根据需要进行操作。这与 List of Packages 条目互斥。 |
-x Exclude List |
指示要从处理范围中排除的软件包的逗号分隔列表或用引号括起来的空格分隔列表。 |
List of Packages |
指示要处理的软件包的列表。 |