应用程序包开发者指南

第 6 章 创建软件包的高级技术

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 设置为以下值之一:


注 –

如果带有参数 -a none 调用了 pkgadd 命令,则该命令始终要求管理员提供基目录。遗憾的是,这还会将文件中的所有参数设置为缺省值 quit,而这可能会导致其他问题。


适应不确定性

管理员可使用管理文件来控制系统上正安装的所有软件包。遗憾的是,软件包设计者经常提供一个替代的缺省管理文件,而没有考虑管理员的愿望。

软件包设计者有时会提供一个替代管理文件,以便他们自己(而不是管理员)能够控制软件包的安装。由于缺省管理文件中的 basedir 条目会覆盖所有其他基目录,因此它提供了一种在安装时选择适当基目录的简单方法。在早于 Solaris 2.5 发行版的所有 Solaris OS 版本中,这种方法被认为是控制基目录的最简单方法。

然而,您必须接受管理员的有关产品安装的希望。提供一个临时的缺省管理文件以便控制安装会导致管理员觉得不受信任。您应该使用 request 脚本和 checkinstall 脚本在管理员的监督下控制这些安装。如果 request 脚本如实地使管理员参与安装过程,System V 打包过程将同时满足管理员和软件包设计者的需求。

使用 BASEDIR 参数

pkginfo 文件都必须以如下所示的条目形式包括一个缺省基目录:


BASEDIR=absolute_path

这只是缺省基目录,可由管理员在安装期间更改。

尽管某些软件包需要多个基目录,但使用此参数定位软件包的好处是,当安装开始的时候,能够保证基目录作为一个有效的目录就位并可写。服务器和客户机的基目录的正确路径以保留环境变量的形式供所有过程脚本使用,并且 pkginfo -r SUNWstuf 命令显示软件包的当前安装基本位置。

checkinstall 脚本中,BASEDIR 即是 pkginfo 文件中定义的该参数(该参数尚未根据条件赋值)。为了检查目标基目录,需要使用 $ {PKG_INSTALL_ROOT} $BASEDIR 构造。这意味着 requestcheckinstall 脚本可以在安装环境下更改 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

使用参数化基目录

如果一个软件包需要多个基目录,您可以使用参数化路径名建立这些目录。此方法已经相当流行,尽管它具有以下缺点。

尽管确定基目录的参数是在 pkginfo 文件中定义的,但可以被 request 脚本修改。这是此方法广受欢迎的主要原因之一。然而,此方法的缺点会长期存在,您应该在迫不得已的情况下才考虑使用此配置。

示例-使用参数化基目录

pkginfo 文件

# 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

pkgmap 文件

: 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 和兼容发行版中的 requestcheckinstall 脚本能够修改 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 脚本所固有的对于生成参数的控制来确保安装成功。

使用 BASEDIR 参数

checkinstall 脚本可以在安装时选择适当的基目录,这意味着可以将基目录放置在目录树中非常低的位置。此示例按顺序递增基目录,从而得到如下形式的目录:/opt/SUNWstuf/opt/SUNWstuf.1/opt/SUNWstuf.2。管理员可以使用 pkginfo 命令确定在每个基目录中所安装的体系结构和版本。

如果 SUNWstuf 软件包(包含一组执行具体工作的实用程序)使用此方法,则它的 pkginfo pkgmap 文件将如下所示。

pkginfo 文件

# 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

pkgmap 文件

: 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

示例-遍历 BASEDIR 的分析脚本

假定 SUNWstuf 的 x86 版本已经安装在服务器上的 /opt/SUNWstuf 中。当管理员使用 pkgadd 命令安装 SPARC 版本时,request 脚本需要检测是否存在 x86 版本并且与管理员就安装问题进行交互。


注 –

可在 checkinstall 脚本中遍历基目录而无需管理员交互,但是,如果此类任意操作的发生过于频繁,管理员会在此过程中失去信心。


软件包的用于处理此情形的 request 脚本和 checkinstall 脚本可能如下所示。

request 脚本

# 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 脚本

# 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 脚本来处理参数化路径,从而遍历基目录。pkginfopkgmap 文件可能如下所示。

pkginfo 文件

# 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

pkgmap 文件

: 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 脚本

# 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 文件

# 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

pkgmap 文件

: 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 文件将如下所示。

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 需要作为该软件包的一部分安装,但不能将其放置到基目录中。假定可重定位软件包是唯一的解决方案,pkginfopkgmap 可能如下所示。

pkginfo 文件

# 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

pkgmap 文件

: 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 下,以便保证新软件包不会干扰现有产品。

复合软件包另一特性

在构建功能复合软件包时,需要遵循两个规则:

换句话说,因为“可重定位”意味着该对象可以安装到任何位置而仍然能够正常工作,所以不能将 init 在引导时运行的任何启动脚本视为可重定位的!尽管在提供的软件包中将 /etc/passwd 指定为相对路径没有任何问题,但它只有一个安装位置。

使绝对路径名看起来像可重定位对象

如果您要构建一个复合软件包,绝对路径必须以不干扰已安装的现有软件的方式工作。可以完全包含在 /opt 中的软件包可避免此问题,因为不存在形成阻碍的现有文件。当 /etc 中的一个文件包括在软件包中时,您必须确保绝对路径名的行为方式与相对路径名的预期行为方式相同。请考虑下面两个示例。

示例-修改文件

说明

将一个条目添加到表中,或者对象是可能要由其他程序或软件包修改的一个新表。

实现

将该对象定义为文件类型 e 并定义为属于 buildawksed 类。执行此任务的脚本必须可与添加自身一样高效地删除自身。

示例

需要将一个条目添加到 /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。可以用以下两种方式之一构建正确的目录。

通过对软件包中的每个绝对对象使用此方法,您可以确信当前的理想行为保持不变或者至少是可恢复的。

示例 -复合软件包

以下是复合软件包的 pkginfopkgmap 文件的示例。

pkginfo 文件

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

pkgmap 文件

: 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,并使用 sedawk 命令为客户机的 /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 命令就会安装这些区别。请查看以下有关修补软件包的准则。

至关重要的一点是,修补程序软件包的版本号必须与原始软件包的版本号相同。您应该使用如下形式的单独 pkginfo 文件条目来跟踪软件包的修补程序状态:


PATCH=patch_number

如果软件包版本在进行修补后发生改变,则相当于您创建了该软件包的另一个实例,这样,管理修补后的产品将变得极为困难。这种渐进式实例修补方法可以将早期 Solaris OS 发行版的某些优势传递下去,但却使更复杂系统的管理变得繁琐。

修补程序中的所有区域参数都必须与软件包中的区域参数相匹配。

对于组成 Solaris OS 的软件包而言,虽然可能有多个修补后的实例,但在软件包数据库中应该只有该软件包的一个副本。为了使用 removef 命令从已安装的软件包中删除某个对象,您需要判断哪些实例拥有该文件。

然而,如果您的软件包(该软件包不属于 Solaris OS)需要确定属于 Solaris OS 的特定软件包的修补级别,那么这就成为一个需要在此处解决的问题。安装脚本可能非常大但不会产生重大影向,因为它们不存储在目标文件系统中。通过类操作脚本和其他各种过程脚本,您可以使用 PKGSAV 环境变量保存经过更改的文件(或者保存到其他某个更加持久的目录),以便能够回退已安装的修补程序。您还可以通过 request 脚本设置适当的环境变量,以监视修补程序历史记录。以下各节中的脚本假定可能存在多个修补程序,其编号方案在应用于单个软件包时具有某种含义。在这种情况下,各个修补程序编号表示软件包内的功能相关文件的一个子集。两个编号不同的修补程序不能更改同一个文件。

为了将一个常规的稀疏软件包转换成修补程序软件包,可将以下各节描述的脚本简单地封装到该软件包中。这些脚本都是公认的标准软件包组件,只有最后两个名为 patch_checkinstallpatch_postinstall 的脚本除外。如果您要包括回退修补程序功能,可以将这两个脚本合并到回退软件包中。这些脚本十分简单,它们的各种任务也容易完成。


注 –

此修补方法可用于修补客户机系统,但服务器上的客户机根目录必须拥有正确的权限,以允许用户 installnobody 读取。


checkinstall 脚本

checkinstall 脚本验证修补程序是否适合于此特定软件包。一旦确认这一点,该脚本将构建修补程序列表修补程序信息列表,然后将它们插入到响应文件中,以便合并到软件包数据库中。

修补程序列表是已经影响当前软件包的修补程序的列表。这一修补程序列表记录在已安装的软件包的 pkginfo 文件中,记录该列表的行可能如下所示:

PATCHLIST=patch_id patch_id ...

修补程序信息列表是当前修补程序所依赖的修补程序的列表。这一修补程序列表也记录在 pkginfo 文件中,记录该列表的行可能如下所示。

PATCH_INFO_103203-01=Installed... Obsoletes:103201-01 Requires: \ Incompatibles: 120134-01

注 –

这些行(及其格式)被声明为公共接口。任何为 Solaris 软件包发行修补程序的公司都应该相应地更新此列表。当修补程序交付时,修补程序内的每个软件包都包含一个执行此任务的 checkinstall 脚本。该同一个 checkinstall 脚本还会更新其他一些特定于修补程序的参数。这是新的修补程序体系结构,称为“直接实例修补”。


在此示例中,原始软件包及其修补程序都存在于同一个目录中。两个原始软件包命名为 SUNWstuf.v1SUNWstuf.v2,而它们的修补程序分别命名为 SUNWstuf.p1SUNWstuf.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 脚本

preinstall 脚本可初始化 prototype 文件、信息文件和安装脚本,以便构建回退软件包。此脚本非常简单,并且此示例中的其余脚本只允许回退软件包描述常规文件。

如果您要在回退软件包中恢复符号链接、硬链接、设备和命名管道,可以修改 preinstall 脚本,以便使用 pkgproto 命令将交付的 pkgmap 文件与安装的文件进行比较,然后为要在回退软件包中更改的每个非文件创建一个 prototype 文件条目。应该使用的方法类似于类操作脚本中的方法。

脚本 patch_checkinstallpatch_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 脚本

postinstall 脚本使用其他脚本提供的信息创建回退软件包。因为 pkgmkpkgtrans 命令不需要软件包数据库,所以可以在软件包安装时执行这些命令。

在此示例中,通过在保存目录中(使用 PKGSAV 环境变量)构建一个流格式软件包来允许撤消修补程序。这并不显而易见,但此软件包必须采用流格式,因为在 pkgadd 操作期间,保存目录会发生移动。如果 pkgadd 命令应用于自己的保存目录中的一个软件包,那么有关软件包源在任何指定时刻所在位置的假设将变得非常不可靠。流格式软件包被解压缩到一个临时目录中并在此处安装。(目录格式软件包将从保存目录开始安装,并且发现自己在 pkgadd 失败安全操作期间突然被重定位。)

要确定应用于软件包的修补程序,请使用以下命令:


$ pkgparam SUNWstuf PATCHLIST

PATCHLIST(一个 Sun 公共接口)之外,此示例中的参数名称中没有什么重要内容。您可以取代 PATCH 使用传统 SUNW_PATCHID,而其他各种列表(例如 PATCH_EXCLPATCH_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

patch_checkinstall 脚本

# 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

patch_postinstall 脚本

# 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 脚本

# 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 脚本

# 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 实用程序的信息。

归档软件包目录的结构

下图中显示的软件包条目表示包含软件包文件的目录。此目录必须与软件包同名。

图 6–1 软件包目录结构

该图显示了直接位于软件包目录下的五个子目录: pkginfo、pkgmap、reloc、root 和 install。此外,还显示了其子目录。

下面列出了软件包目录内包含的文件和目录的功能。

项 

说明 

pkginfo

文件,对软件包进行总体描述,包括特殊环境变量和安装指令 

pkgmap

描述每个要安装对象的文件,如文件、目录或管道 

reloc

可选目录,包含要相对于基目录安装的文件(可重定位的对象) 

root

可选目录,包含要相对于 root 目录安装的文件(根对象)

install

可选目录,包含脚本和其他辅助文件(除了 pkginfopkgmap,所有 ftype i 文件都位于此处)

使用类归档格式,软件包生成器可以将 relocroot 目录中的文件组合到归档文件中,然后对其进行压缩、加密或以所需的任何方式进行其他处理,以便提高安装速度,减小软件包大小,或者增加软件包的安全性。

ABI 允许将软件包内的任何文件指定给某个类。特定类中的所有文件都可以使用类操作脚本定义的自定义方法安装到磁盘中。此自定义方法可以利用目标系统中提供的程序或随软件包一起提供的程序。得到的格式很像标准 ABI 格式。如下图所示,另一个目录被添加。将要归档的任何文件类只是组合为单个文件,并且放置到 archive 目录中。系统将从 relocroot 目录中删除所有归档文件,并且将一个安装类操作脚本放置到 install 目录中。

图 6–2 归档软件包目录结构

该图显示了图 6-1 中的同一个软件包目录结构,但增加了 archive 子目录。

支持类归档软件包的关键字

为了支持这一新的类归档格式,三个采用关键字形式的新接口在 pkginfo 文件内具有特殊含义。这些关键字用于指定需要特殊处理的类。每个关键字语句的格式为: keyword=class1[class2 ...]。下表定义了每个关键字值。

关键字 

说明 

PKG_SRC_NOVERIFY

如果所交付的软件包的 relocroot 目录中的文件属于指定类,则该关键字告诉 pkgadd 不要验证这些文件是否存在以及文件属性。所有归档类都需要此关键字,因为这些文件不再位于 relocroot 目录中。它们是 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 实用程序

faspac 实用程序将标准 ABI 软件包转换为随附软件包所使用的类归档格式。此实用程序使用 cpio 进行归档,使用 compress 进行压缩。生成的软件包在顶层目录中有一个名为 archive 的附加目录。在此目录中,将包含按类命名的所有归档文件。install 目录将包含解压缩每个归档文件所需的类操作脚本。绝对路径将不进行归档。

faspac 实用程序具有以下格式:


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

指示要处理的软件包的列表。