Application Packaging Developer's Guide

Chapter 6 Advanced Techniques for Creating Packages

The full capabilities of System V packaging as implemented in the Solaris OS provide a powerful tool for the installation of software products. As a package designer, you can take advantage of these capabilities. Packages that are not part of the Solaris OS (unbundled packages) may use the class mechanism to customize server/client installations. Relocatable packages can be designed to accommodate the desires of the administrator. A complex product can be delivered as a set of composite packages that automatically resolve package dependencies. Upgrading and patching may be customized by the package designer. Patched packages can be delivered in the same way as unpatched packages, and the backout archives can also be included in the product.

This is a list of the overview information in this chapter.

Specifying the Base Directory

You can use several methods to specify where a package will be installed, and it is important to be able to change the installation base dynamically at install time. If this is accomplished correctly, an administrator can install multiple versions and multiple architectures without complications.

This section discusses common methods first, followed by approaches that enhance installations to heterogeneous systems.

The Administrative Defaults File

Administrators responsible for installing packages can use administration files to control package installation. However, as a package designer, you need to know about administration files and how an administrator can alter your intended package installation.

An administration file tells the pkgadd command whether to perform any of the checks or prompts that it normally does. Consequently, administrators should fully understand a package's installation process and the scripts involved before using administration files.

A basic administrative defaults file is shipped with the SunOS operating system in /var/sadm/install/admin/default. This is the file that establishes the most basic level of administrative policy as regards the installation of software products. The file looks like this as shipped:

#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

The administrator may edit this file to establish new default behaviors, or create a different administration file and specify its existence by using the -a option to the pkgadd command.

Eleven parameters can be defined in an administration file, but not all need to be defined. For more information, see admin(4).

The basedir parameter specifies how the base directory will be derived when a package is installed. Most administrators leave this as default, but basedir can be set to one of the following:


Note –

If the pkgadd command is called with the argument -a none, it always asks the administrator for a base directory. Unfortunately, this also sets all parameters in the file to the default value of quit, which can result in additional problems.


Becoming Comfortable With Uncertainty

An administrator has control over all packages being installed on a system by using an administration file. Unfortunately, an alternate administrative defaults file is often provided by the package designer, bypassing the wishes of the administrator.

Package designers sometimes include an alternate administration file so that they, not the administrator, control a package's installation. Because the basedir entry in the administrative defaults file overrides all other base directories, it provides a simple method for selecting the appropriate base directory at install time. In all versions of the Solaris OS prior to the Solaris 2.5 release, this was considered the simplest method for controlling the base directory.

However, it is necessary for you to accept the administrator's desires regarding the installation of the product. Providing a temporary administrative defaults file for the purpose of controlling the installation leads to mistrust on the part of administrators. You should use a request script and checkinstall script to control these installations under the supervision of the administrator. If the request script faithfully involves the administrator in the process, System V packaging will serve both administrators and package designers.

Using the BASEDIR Parameter

The pkginfo file for any relocatable package must include a default base directory in the form of an entry like this:


BASEDIR=absolute_path

This is only the default base directory; it can be changed by the administrator during installation.

While some packages require more than one base directory, the advantage to using this parameter to position the package is because the base directory is guaranteed to be in place and writable as a valid directory by the time installation begins. The correct path to the base directory for the server and client is available to all procedure scripts in the form of reserved environment variables, and the pkginfo -r SUNWstuf command displays the current installation base for the package.

In the checkinstall script, BASEDIR is the parameter exactly as defined in the pkginfo file (it has not been conditioned yet). In order to inspect the target base directory, the ${PKG_INSTALL_ROOT}$BASEDIR construction is required. This means that the request or checkinstall script can change the value of BASEDIR in the installation environment with predictable results. By the time the preinstall script is called, the BASEDIR parameter is the fully conditioned pointer to the actual base directory on the target system, even if the system is a client.


Note –

The request script utilizes the BASEDIR parameter differently for different releases of the SunOS operating system. In order to test a BASEDIR parameter in a request script, the following code should be used to determine the actual base directory in use.

# request script
constructs base directory
if [ ${CLIENT_BASEDIR} ]; then
	  LOCAL_BASE=$BASEDIR
else
	  LOCAL_BASE=${PKG_INSTALL_ROOT}$BASEDIR
fi

Using Parametric Base Directories

If a package requires multiple base directories, you can establish them with parametric path names. This method has become quite popular, although it has the following drawbacks.

While the parameters that determine the base directories are defined in the pkginfo file, they may be modified by the request script. That is one of the primary reasons for the popularity of this approach. The drawbacks, however are chronic and you should consider this configuration a last resort.

Examples—Using Parametric Base Directories

The pkginfo File

# 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

The pkgmap File

: 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

Managing the Base Directory

Any package that is available in multiple versions or for multiple architectures should be designed to walk the base directory, if needed. Walking a base directory means that if a previous version or a different architecture of the package being installed already exists in the base directory, the package being installed resolves this issue, perhaps by creating a new base directory with a slightly different name. The request and checkinstall scripts in the Solaris 2.5 and compatible releases have the ability to modify the BASEDIR environment variable. This is not true for any prior version of the Solaris OS.

Even in older versions of the Solaris OS, the request script had the authority to redefine directories within the installation base. The request script can do this in a way that still supports most administrative preferences.

Accommodating Relocation

While you can select base directories for various packages that are guaranteed unique to an architecture and version, this leads to unnecessary levels of directory hierarchy. For example, for a product designed for SPARC and x86 based processors, you could organize the base directories by processor and version as shown below.

Base Directory 

Version and Processor 

/opt/SUNWstuf/sparc/1.0

Version 1.0, SPARC 

/opt/SUNWstuf/sparc/1.2

Version 1.2, SPARC 

/opt/SUNWstuf/x86/1.0

Version 1.0, x86 

This is okay and it does work, but you are treating names and numbers as though they mean something to the administrator. A better approach is to do this automatically after explaining it to the administrator and obtaining permission.

This means that you can do the whole job in the package without requiring the administrator to do it manually. You can assign the base directory arbitrarily and then transparently establish the appropriate client links in a postinstall script. You can also use the pkgadd command to install all or part of the package to the clients in the postinstall script. You can even ask the administrator which users or clients need to know about this package and automatically update PATH environment variables and /etc files. This is completely acceptable as long as whatever the package does upon installation, it undoes upon removal.

Walking Base Directories

You can take advantage of two methods for controlling the base directory at install time. The first is best for new packages that will install only to Solaris 2.5 and compatible releases; it provides very useful data for the administrator and supports multiple installed versions and architectures and requires minimal special work. The second method can be used by any package and makes use of the request script's inherent control over build parameters to ensure successful installations.

Using the BASEDIR Parameter

The checkinstall script can select the appropriate base directory at install time, which means that the base directory can be placed very low in the directory tree. This example increments the base directory sequentially, leading to directories of the form /opt/SUNWstuf, /opt/SUNWstuf.1, and /opt/SUNWstuf.2. The administrator can use the pkginfo command to determine which architecture and version are installed in each base directory.

If the SUNWstuf package (containing a set of utilities that do stuff) uses this method, its pkginfo and pkgmap files would look like this.

The pkginfo File

# 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

The pkgmap File

: 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

Example—Analysis Scripts That Walk a BASEDIR

Assume that the x86 version of SUNWstuf is already installed on the server in /opt/SUNWstuf. When the administrator uses the pkgadd command to install the SPARC version, the request script needs to detect the existence of the x86 version and interact with the administrator regarding the installation.


Note –

The base directory could be walked without administrator interaction in a checkinstall script, but if arbitrary operations like this happen too often, administrators lose confidence in the process.


The request script and checkinstall script for a package that handle this situation might look like this.

The request Script

# 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

The checkinstall Script

# checkinstall
script for SUNWstuf to politely suspend
 
grep quitinstall $1
if [ $? -eq 0 ]; then
	exit 3		# politely suspend installation
fi
 
exit 0

This approach would not work very well if the base directory was simply /opt. This package has to call out the BASEDIR more precisely since /opt would be difficult to walk. In fact, depending on the mount scheme, it may not be possible. The example walks the base directory by creating a new directory under /opt, which does not introduce any problems.

This example uses a request script and a checkinstall script even though versions of Solaris prior to the 2.5 release cannot run a checkinstall script. The checkinstall script in this example is used for the purpose of politely halting the installation in response to a private message in the form of the string quitinstall. If this script executes under the Solaris 2.3 release, the checkinstall script is ignored and the request script halts the installation with an error message.

Remember that prior to the Solaris 2.5 and compatible releases, the BASEDIR parameter is a read-only parameter and cannot be changed by the request script. For this reason, if an old version of the SunOS operating system is detected (by testing for a conditioned CLIENT_BASEDIR environment variable), the request script has only two options—continue or quit.

Using Relative Parametric Paths

If your software product might be installed on older versions of the SunOS operating system, the request script needs to do all the necessary work. This approach can also be used to manipulate multiple directories. If additional directories are required, they still need to be included under a single base directory in order to provide an easily administrable product. While the BASEDIR parameter does not provide the level of granularity available in the latest Solaris release, your package can still walk the base directory by using the request script to manipulate parametric paths. This is how the pkginfo and pkgmap files might look.

The pkginfo File

# 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

The pkgmap File

: 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

This example is not perfect. A pkginfo -r command returns /opt for the installation base, which is pretty ambiguous. Many packages are in /opt, but at least it is a meaningful directory. Just like the previous example, this next example fully supports multiple architectures and versions. The request script can be tailored to the needs of the specific package and resolve whatever dependencies are applicable.

Example—A request Script That Walks a Relative Parametric Path

# 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

Supporting Relocation in a Heterogeneous Environment

The original concept behind System V packaging assumed one architecture per system. The concept of a server did not play a role in the design. Now, of course, a single server may provide support for several architectures, which means there may be several copies of the same software on a server, each for a different architecture. While Solaris packages are sequestered within recommended file system boundaries (for example, / and /usr), with product databases on the server as well as each client, not all installations necessarily support this division. Certain implementations support an entirely different structure and imply a common product database. While pointing the clients to different versions is straightforward, actually installing System V packages to different base directories can introduce complications for the administrator.

When you design your package, you should also consider the common methods administrators use for introducing new software versions. Administrators often seek to install and test the latest version side-by-side with the currently installed version. The procedure involves installing the new version to a different base directory than the current version and directing a handful of non-critical clients to the new version as a test. As confidence builds, the administrator redirects more and more clients to the new version. Eventually, the administrator retains the old version only for emergencies and then finally deletes it.

What this means is that packages destined for modern heterogeneous systems must support true relocation in the sense that the administrator may put them any reasonable place on the file system and still see full functionality. The Solaris 2.5 and compatible releases provide a number of useful tools which allow multiple architectures and versions to install cleanly to the same system. Solaris 2.4 and compatible versions also support true relocation but accomplishing the task is not quite as obvious.

Traditional Approach

Relocatable Packages

The System V ABI implies that the original intention behind the relocatable package was to make installing the package more convenient for the administrator. Now the need for relocatable packages goes much further. Convenience is not the only issue, rather it is quite possible that during the installation an active software product is already installed in the default directory. A package that is not designed to deal with this situation either overwrites the existing product or fails to install. However, a package designed handle multiple architectures and multiple versions can install smoothly and offer the administrator a wide range of options that are fully compatible with existing administrative traditions.

In some ways the problem of multiple architectures and the problem of multiple versions is the same. It must be possible to install a variant of the existing package side by side with other variants, and direct clients or standalone consumers of exported file systems to any one of those variants, without degraded functionality. While Sun has established methods for dealing with multiple architectures on a server, the administrator may not adhere to those recommendations. All packages need to be capable of complying with the administrators' reasonable wishes regarding installation.

Example-A Traditional Relocatable Package

This example shows what a traditional relocatable package may look like. The package is to be located in /opt/SUNWstuf, and its pkginfo file and pkgmap file might look like this.

The pkginfo File

# 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

The pkgmap File

: 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

This is referred to as the traditional method because every package object is installed to the base directory defined by the BASEDIR parameter from the pkginfo file. For example, the first object in the pkgmap file is installed as the directory /opt/SUNWstuf.

Absolute Packages

An absolute package is one that installs to a particular root (/) file system. These packages are difficult to deal with from the standpoint of multiple versions and architectures. As a general rule, all packages should be relocatable. There are, however very good reasons to include absolute elements in a relocatable package.

Example-A Traditional Absolute Package

If the SUNWstuf package was an absolute package, the BASEDIR parameter should not be defined in the pkginfo file, and the pkgmap file would look like this.

The pkgmap File

: 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

In this example, if the administrator specified an alternate base directory during installation, it would be ignored by the pkgadd command. This package always installs to /opt/SUNWstuf of the target system.

The -R argument to the pkgadd command works as expected. For example,


pkgadd -d . -R /export/opt/client3 SUNWstuf

installs the objects in /export/opt/client3/opt/SUNWstuf; but that is the closest this package comes to being relocatable.

Notice the use of the question mark (?) for the /opt directory in the pkgmap file. This indicates that the existing attributes should not be changed. It does not mean “create the directory with default attributes,” although under certain circumstances that may happen. Any directory that is specific to the new package must specify all attributes explicitly.

Composite Packages

Any package containing relocatable objects is referred to as a relocatable package. This can be misleading because a relocatable package may contain absolute paths in its pkgmap file. Using a root (/) entry in a pkgmap file can enhance the relocatable aspects of the package. Packages that have both relocatable and root entries are called composite packages.

Example-A Traditional Solution

Assume that one object in the SUNWstuf package is a startup script executed at run level 2. The file /etc/rc2.d/S70dostuf needs to be installed as a part of the package, but it cannot be placed into the base directory. Assuming that a relocatable package is the only solution, the pkginfo and a pkgmap might look like this.

The pkginfo File

# 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

The pkgmap File

: 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

There is not much difference between this approach and that of the absolute package. In fact, this would be better off as an absolute package—if the administrator provided an alternate base directory for this package, it would not work!

In fact, only one file in this package needs to be root-relative, the rest could be moved anywhere. How to solve this problem through the use of a composite package is discussed throughout the remainder of this section.

Beyond Tradition

The approach described in this section does not apply to all packages, but it does result in improved performance during installation to an heterogeneous environment. Very little of this applies to packages that are delivered as part of the Solaris OS (bundled packages); however, unbundled packages can practice non-traditional packaging.

The reason behind encouraging relocatable packages is to support this requirement:

When a package is added or removed, the existing desirable behaviors of installed software products will be unchanged.

Unbundled packages should reside under /opt so as to assure that the new package does not interfere with existing products.

Another Look at Composite Packages

There are two rules to follow when constructing a functional composite package:

In other words, since “relocatable” means the object can be installed anywhere and still work, no startup script run by init at boot time can be considered relocatable! While there is nothing wrong with specifying /etc/passwd as a relative path in the delivered package, there is only one place it can go.

Making Absolute Path Names Look Relocatable

If you are going to construct a composite package, the absolute paths must operate in a manner which does not interfere with existing installed software. A package that can be entirely contained in /opt gets around this problem since there are no existing files in the way. When a file in /etc is included in the package, you must ensure that the absolute path names behave in the same way that is expected from relative path names. Consider the following two examples.

Example—Modifying a File

Description

An entry is being added to a table, or the object is a new table which is likely to be modified by other programs or packages.

Implementation

Define the object as file type e and belonging to the build, awk, or sed class. The script that performs this task must remove itself as effectively as it adds itself.

Example

An entry needs to be added to /etc/vfstab in support of the new solid state hard disk.

The entry in the pkgmap file might be


1 e sed /etc/vfstab ? ? ?

The request script asks the operator if /etc/vfstab should be modified by the package. If the operator answers “no” then the request script will print instructions on how to do the job manually and will execute


echo "CLASSES=none" >> $1

If the operator answers “yes” then it executes


echo "CLASSES=none sed" >> $1

which activates the class action script that will make the necessary modifications. The sed class means that the package file /etc/vfstab is a sed program which contains both the install and remove operations for the same-named file on the target system.

Example—Creating a New File

Description

The object is an entirely new file that is unlikely to be edited at a later time or, it is replacing a file owned by another package.

Implementation

Define the package object as file type f and install it using a class action script capable of undoing the change.

Example

A brand new file is required in /etc to provide the necessary information to support the solid state hard disk, named /etc/shdisk.conf. The entry in the pkgmap file might look like this:


 
.
.
.
1 f newetc /etc/shdisk.conf
.
.
.

The class action script i.newetc is responsible for installing this and any other files that need to go into /etc. It checks to make sure there is not another file there. If there is not, it will simply copy the new file into place. If there is already a file in place, it will back it up before installing the new file. The script r.newetc removes these files and restores the originals, if required. Here is the key fragment of the install script.

# 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

Notice that this script uses the PKGSAV environment variable to store a backup of the file to be replaced. When the argument ENDOFCLASS is passed to the script, that is the pkgadd command informing the script that these are the last entries in this class, at which point the script archives and compresses the files that were saved using a private compression program stored in the install directory of the package.

While the use of the PKGSAV environment variable is not reliable during a package update; if the package is not updated (through a patch, for instance) the backup file is secure. The following remove script includes code to deal with the other issue—the fact that older versions of the pkgrm command do not pass the scripts the correct path to the PKGSAV environment variable.

The removal script might look like this.

# 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

This script uses a private uninstalled algorithm (unsquish) which is in the install directory of the package database. This is done automatically by the pkgadd command at install time. All scripts not specifically recognized as install-only by the pkgadd command are left in this directory for use by the pkgrm command. You cannot count on where that directory is, but you can depend on the directory being flat and containing all appropriate information files and installation scripts for the package. This script finds the directory by virtue of the fact that the class action script is guaranteed to be executing from the directory that contains the unsquish program.

Notice, also, that this script does not just assume the target directory is /etc. It may actually be /export/root/client2/etc. The correct directory could be constructed in one of two ways.

By using this approach for each absolute object in the package, you can be sure that the current desirable behavior is unchanged or at least recoverable.

Example—A Composite Package

This is an example of the pkginfo and pkgmap files for a composite package.

The 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 daemon
PSTAMP=hubert990707141632

The pkgmap File

: 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

While S70dostuf belongs to the daemon class, the directories that lead up to it (which are already in place at install time) belong to the none class. Even if the directories were unique to this package, you should leave them in the none class. The reason for this is that the directories need to be created first and deleted last, and this is always true for the none class. The pkgadd command creates directories; they are not copied from the package and they are not passed to a class action script to be created. Instead, they are created by the pkgadd command before it calls the install class action script, and the pkgrm command deletes directories after completion of the removal class action script.

This means that if a directory in a special class contains objects in the class none, when the pkgrm command attempts to remove the directory, it fails because the directory will not be empty in time. If an object of class none is to be inserted into a directory of some special class, that directory will not exist in time to accept the object. The pkgadd command will create the directory on-the-fly during installation of the object and may not be able to synchronize the attributes of that directory when it finally sees the pkgmap definition.


Note –

When assigning a directory to a class, always remember the order of creation and deletion.


Making Packages Remotely Installable

All packages must be installable remotely. Installable remotely means you do not assume the administrator installing your package is installing to the root (/) file system of the system running the pkgadd command. If, in one of your procedure scripts, you need to get to the /etc/vfstab file of the target system, you need to use the PKG_INSTALL_ROOT environment variable. In other words, the path name /etc/vfstab will get you to the /etc/vfstab file of the system running the pkgadd command, but the administrator may be installing to a client at /export/root/client3. The path ${PKG_INSTALL_ROOT}/etc/vfstab is guaranteed to get you to the target file system.

Example – Installing to a Client System

In this example, the SUNWstuf package is installed to client3, which is configured with /opt in its root (/) file system. One other version of this package is already installed on client3, and the base directory is set to basedir=/opt/$PKGINST from an administration file, thisadmin. (For more information on administration files, see The Administrative Defaults File.) The pkgadd command executed on the server is:


# pkgadd -a thisadmin -R /export/root/client3 SUNWstuf

The table below lists the environment variables and their values that are passed to the procedure scripts.

Table 6–1 Values Passed to Procedure Scripts

Environment Variable 

Value 

PKGINST

SUNWstuf.2

PKG_INSTALL_ROOT

/export/root/client3

CLIENT_BASEDIR

/opt/SUNWstuf.2

BASEDIR

/export/root/client3/opt/SUNWstuf.2

Example – Installing to a Server or Standalone System

To install to the server or a standalone system under the same circumstances as the previous example, the command is:


# pkgadd -a thisadmin SUNWstuf

The table below lists the environment variables and their values that are passed to the procedure scripts.

Table 6–2 Values Passed to Procedure Scripts

Environment Variable 

Value 

PKGINST

SUNWstuf.2

PKG_INSTALL_ROOT

Not defined. 

CLIENT_BASEDIR

/opt/SUNWstuf.2

BASEDIR

/opt/SUNWstuf.2

Example – Mounting Shared File Systems

Assume that the SUNWstuf package creates and shares a file system on the server at /export/SUNWstuf/share. When the package is installed to the client systems, their /etc/vfstab files need to be updated to mount this shared file system. This is a situation where you can use the CLIENT_BASEDIR variable.

The entry on the client needs to present the mount point with reference to the client's file system. This line should be constructed correctly whether the installation is from the server or from the client. Assume that the server's system name is $SERVER. You can go to $PKG_INSTALL_ROOT/etc/vfstab and, using the sed or awk commands, construct the following line for the client's /etc/vfstab file.


$SERVER:/export/SUNWstuf/share - $CLIENT_BASEDIR/usr nfs - yes ro

For example, for the server universe and the client system client9, the line in the client system's /etc/vfstab file would look like:


universe:/export/SUNWstuf/share - /opt/SUNWstuf.2/usr nfs - yes ro

Using these parameters correctly, the entry always mounts the client's file system, whether it is being constructed locally or from the server.

Patching Packages

A patch to a package is just a sparse package designed to overwrite certain files in the original. There is no real reason for shipping a sparse package except to save space on the delivery medium. You could also ship the entire original package with a few files changed, or provide access to the modified package over a network. As long as only those new files are actually different (the other files were not recompiled), the pkgadd command installs the differences. Review the following guidelines regarding patching packages.

It is crucial that the version number of the patch package be the same as that of the original package. You should keep track of the patch status of the package using a separate pkginfo file entry of the form.


PATCH=patch_number

If the package version is changed for a patch, you create another instance of the package and it becomes extremely difficult to manage the patched product. This method of progressive instance patching carried certain advantages in the early releases of the Solaris OS, but makes management of more complicated systems tedious.

All of the zone parameters in the patch must match the zone parameters in the package.

As far as the packages that make up the Solaris OS are concerned, there should be only one copy of the package in the package database, although there may be multiple patched instances. In order to remove an object from an installed package (using the removef command) you need to figure out what instances own that file.

However, if your package (that is not part of the Solaris OS) needs to determine the patch level of a particular package that is part of the Solaris OS, this becomes a problem to be resolved here. The installation scripts can be quite large without significant impact since they are not stored on the target file system. Using class action scripts and various other procedure scripts, you can save changed files using the PKGSAV environment variable (or to some other, more permanent directory) in order to allow backing out installed patches. You can also monitor patch history by setting appropriate environment variables through the request scripts. The scripts in the next sections assume that there may be multiple patches whose numbering scheme carries some meaning when applied to a single package. In this case, individual patch numbers represent a subset of functionally related files within the package. Two different patch numbers cannot change the same file.

In order to make a regular sparse package into a patch package, the scripts described in the following sections can simply be folded into the package. All of them are recognizable as standard package components with the exception of the last two which are named patch_checkinstall and patch_postinstall. Those two scripts can be incorporated into the backout package, if you want to include the ability to back out the patch. The scripts are fairly simple and their various tasks are straightforward.


Note –

This method of patching can be used to patch client systems, but client root directories on the server must have the correct permissions to allow reading by the user install or nobody.


The checkinstall Script

The checkinstall script verifies that the patch is appropriate for this particular package. Once that is confirmed, it constructs the patch list and the patch info list, and then inserts them into the response file for incorporation into the package database.

A patch list is the list of patches that have affected the current package. This list of patches is recorded in the installed package in the pkginfo file with a line that might look like this:

PATCHLIST=patch_id patch_id ...

A patch info list is the list of patches on which the current patch is dependent. This list of patches is also recorded in the pkginfo file with a line that might look like this.

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

Note –

These lines (and their format) are declared as a public interface. Any company that ships patches for Solaris packages should update this list appropriately. When a patch is delivered, each package within the patch contains a checkinstall script that performs this task. That same checkinstall script also updates some other patch-specific parameters. This is the new patch architecture, which is called Direct Instance Patching.


In this example, both the original packages and their patches exist in the same directory. The two original packages are named SUNWstuf.v1 and SUNWstuf.v2, and their patches are named SUNWstuf.p1 and SUNWstuf.p2. What this means is that it could be very difficult for a procedure script to figure out what directory these files came from, since everything in the package name after the dot (“.”) is stripped for the PKG parameter, and the PKGINST environment variable refers to the installed instance not the source instance. So the procedure scripts can find the source directory, the checkinstall script (which is always executed from the source directory) makes the inquiry and passes the location on as the variable SCRIPTS_DIR. If there had been only one package in the source directory called SUNWstuf, then the procedure scripts could have found it using $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

The preinstall Script

The preinstall script initializes the prototype file, information files, and installation scripts for the backout package to be constructed. This script is very simple and the remaining scripts in this example only allow a backout package to describe regular files.

If you wanted to restore symbolic links, hard links, devices, and named pipes in a backout package, you could modify the preinstall script to use the pkgproto command to compare the delivered pkgmap file with the installed files, and then create a prototype file entry for each non-file to be changed in the backout package. The method you should use is similar to the method in the class action script.

The scripts patch_checkinstall and patch_postinstall are inserted into the package source tree from the preinstall script. These two scripts undo what the patch does.

# 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

The Class Action Script

The class action script creates a copy of each file that replaces an existing file and adds a corresponding line to the prototype file for the backout package. This is all done with fairly simple nawk scripts. The class action script receives a list of source/destination pairs consisting of ordinary files that do not match the corresponding installed files. Symbolic links and other non-files must be dealt with in the preinstall script.

# 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

The postinstall Script

The postinstall script creates the backout package using the information provided by the other scripts. Since the pkgmk and pkgtrans commands do not require the package database, they can be executed within a package installation.

In the example, undoing the patch is permitted by constructing a stream format package in the save directory (using the PKGSAV environment variable). It is not obvious, but this package must be in stream format, because the save directory gets moved around during a pkgadd operation. If the pkgadd command is applied to a package in its own save directory, assumptions about where the package source is at any given time become very unreliable. A stream format package is unpacked into a temporary directory and installed from there. (A directory format package would begin installing from the save directory and find itself suddenly relocated during a pkgadd fail-safe operation.)

To determine which patches are applied to a package, use this command:


$ pkgparam SUNWstuf PATCHLIST

With the exception of PATCHLIST, which is a Sun public interface, there is nothing significant in the parameter names in this example. Instead of PATCH you could use the traditional SUNW_PATCHID and the various other lists such as PATCH_EXCL and PATCH_REQD could be renamed accordingly.

If certain patch packages depend upon other patch packages which are available from the same medium, the checkinstall script could determine this and create a script to be executed by the postinstall script in the same way that the upgrade example (see Upgrading Packages) does.

# 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

The patch_checkinstall Script

# 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

The patch_postinstall Script

# 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

Upgrading Packages

The process of upgrading a package is very different from that of overwriting a package. While there are special tools to support the upgrade of standard packages delivered as part of the Solaris OS, an unbundled package can be designed to support its own upgrade—several previous examples described packages that look ahead and control the precise method of installation under the direction of the administrator. You can design the request script to support direct upgrade of a package as well. If the administrator chooses to have one package install so as to completely replace another, leaving no residual obsolete files, the package scripts can do this.

The request script and postinstall script in this example provide a simple upgradable package. The request script communicates with the administrator and then sets up a simple file in the /tmp directory to remove the old package instance. (Although the request script creates a file (which is forbidden), it is okay because everyone has access to /tmp).

The postinstall script then executes the shell script in /tmp, which executes the necessary pkgrm command against the old package and then deletes itself.

This example illustrates a basic upgrade. It is less than fifty lines of code including some fairly long messages. It could be expanded to backout the upgrade or make other major transformations to the package as required by the designer.

The design of the user interface for an upgrade option must be absolutely sure that the administrator is fully aware of the process and has actively requested upgrade rather than parallel installation. There is nothing wrong with performing a well understood complex operation like upgrade as long as the user interface makes the operation clear.

The request Script

# 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

The postinstall Script

# 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

Creating Class Archive Packages

A class archive package, which is an enhancement to the Application Binary Interface (ABI), is one in which certain sets of files have been combined into single files, or archives, and optionally compressed or encrypted. Class archive formats increase initial install speed by up to 30% and improves reliability during installation of packages and patches onto potentially active file systems.

The following sections provide information about the archive package directory structure, keywords, and faspac utility.

Structure of the Archive Package Directory

The package entry shown in the figure below represents the directory containing the package files. This directory must be the same name as the package.

Figure 6–1 Package Directory Structure

Diagram shows five subdirectories directly under the
package directory: pkginfo, pkgmap, reloc, root, and install. Also shows their
subdirectories.

The following lists the functions of the files and directories contained within the package directory.

Item 

Description 

pkginfo

File describing the package as a whole including special environment variables and installation directives 

pkgmap

File describing each object to be installed, such as a file, directory, or pipe 

reloc

Optional directory containing the files to be installed relative to the base directory (the relocatable objects) 

root

Optional directory containing the files to be installed relative to the root directory (the root objects)

install

Optional directory containing scripts and other auxiliary files (except for pkginfo and pkgmap, all ftype i files to here)

The class archive format allows the package builder to combine files from the reloc and root directories into archives which can be compressed, encrypted, or otherwise processed in any desired way in order to increase install speed, reduce package size, or increase package security.

The ABI allows any file within a package to be assigned to a class. All files within a specific class may be installed to the disk using a custom method defined by a class action script. This custom method may make use of programs available on the target system or programs delivered with the package. The resulting format looks much like the standard ABI format. As shown in the following illustration, another directory is added. Any class of files intended for archive is simply combined into a single file and placed into the archive directory. All archived files are removed from the reloc and root directories and an install class action script is placed into the install directory.

Figure 6–2 Archive Package Directory Structure

Diagram shows the same package directory structure in
Figure 6-1 with the addition of the archive subdirectory.

Keywords to Support Class Archive Packages

In order to support this new class archive format, three new interfaces in the form of keywords have special meaning within the pkginfo file. These keywords are used to designate classes requiring special treatment. The format of each keyword statement is: keyword=class1[class2 class3 ...]. Each keyword values are defined in the following table.

Keyword 

Description 

PKG_SRC_NOVERIFY

This tells pkgadd not to verify the existence and properties of the files in the delivered package's reloc or root directories if they belong to the named class. This is required for all archived classes, because those files are no longer in a reloc or root directory. They are a private format file in the archive directory.

PKG_DST_QKVERIFY

The files in these classes are verified after installation using a quick algorithm with little to no text output. The quick verify first sets each file's attributes correctly and then checks to see if the operation succeeded. There is then a test of the file size and modification time against the pkgmap. No checksum verification is performed and there is poorer error recovery than that provided by the standard verification mechanism. In the event of a power outage or disk failure during installation, the contents file may be inconsistent with the installed files. This inconsistency can always be resolved with a pkgrm.

PKG_CAS_PASSRELATIVE

Normally the install class action script receives from stdin a list of source and destination pairs telling it which files to install. The classes assigned to PKG_CAS_PASSRELATIVE do not get the source and destination pairs. Instead they receive a single list, the first entry of which is the location of the source package and the rest of which are the destination paths. This is specifically for the purpose of simplifying extraction from an archive. From the location of the source package, you can find the archive in the archive directory. The destination paths are then passed to the function responsible for extracting the contents of the archive. Each destination path provided is either absolute or relative to the base directory depending on whether the path was located in root or reloc originally. If this option is chosen, it may be difficult to combine both relative and absolute paths into a single class.

For each archived class a class action script is required. This is a file containing Bourne shell commands which is executed by pkgadd to actually install the files from the archive. If a class action script is found in the install directory of the package, pkgadd turns all responsibility for installation over to that script. The class action script is run with root permissions and can place its files just about anywhere on the target system.


Note –

The only keyword that is absolutely necessary in order to implement a class archive package is PKG_SRC_NOVERIFY. The others may be used to increase installation speed or conserve code.


The faspac Utility

The faspac utility converts a standard ABI package into a class archive format used for bundled packages. This utility archives using cpio and compresses using compress. The resulting package has an additional directory in the top directory called archive. In this directory will be all of the archives named by class. The install directory will contain the class action scripts necessary to unpack each archive. Absolute paths are not archived.

The faspac utility has the following format:


faspac [-m Archive Method] -a -s -q [-d Base Directory] /
[-x Exclude List] [List of Packages]

Each faspac command option is described in the following table.

Option 

Description 

-m Archive Method

 

Indicates a method for archive or compression. bzip2 is the default compression utilities used. To switch to zip or unzip method use -m zip or for cpio or compress use -m cpio.

-a

Fixes attributes (must be root to do this). 

-s

Indicates standard ABI-type package translation. This option takes a cpio or compresssed packaged and makes it a standard ABI-compliant package format. 

-q

Indicates quiet mode. 

-d Base Directory

Indicates the directory in which all packages present will be acted upon as required by the command line. This is mutually exclusive with the List of Packages entry.

-x Exclude List

Indicates a comma-separated or quoted, space-separated list of packages to exclude from processing. 

List of Packages

Indicates the list of packages to be processed.