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:
-
ask, which means always ask the administrator
for a base directory
-
An absolute path name
-
An absolute path name containing the $PKGINST construction,
which means always install to a base directory derived from the package instance
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:
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.
-
A package with parametric path names usually behaves like
an absolute package but is treated by the pkgadd command
like a relocatable package. The BASEDIR parameter must be defined
even if it is not used.
-
The administrator cannot ascertain the installation base for
the package using the System V utilities (the pkginfo -r command
will not work).
-
The administrator cannot use the established method to relocate
the package (it is called relocatable but it acts absolute).
-
Multiple architecture or multiple version installations require
contingency planning for each of the target base directories which often means
multiple complex class action scripts.
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:
-
Establish the base directory based upon where the vast majority
of the package objects go.
-
If a package object goes into a common directory that is not
the base directory (for example, /etc), specify it as
an absolute path name in the prototype file.
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.
-
Use the ${PKG_INSTALL_ROOT}/etc construction,
or.
-
Take the directory name of a file passed by the pkgadd command
(which is what this script does).
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.
-
If the system is complex enough, it is wise to establish a
patch identification system which assures that no two patches replace the
same file in an attempt to correct different aberrant behaviors. For instance,
Sun patch base numbers are assigned mutually exclusive sets of files for which
they are responsible.
-
It is necessary to be able to back out a patch.
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.
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
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
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.
|