Sun Microsystems, Inc.
spacerspacer
spacer www.sun.com docs.sun.com |
spacer
black dot
 
 
6.  Advanced Package Creation Techniques Supporting Relocation in a Heterogeneous Environment Beyond Tradition Example--Creating a New File  Previous   Contents   Next 
   
 

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 Installable Remotely

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

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

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.

  • A patch must not change the intended delivered behavior of the package--it is not a mechanism for installing new features. A patch is used to repair objects installed on the system.

  • 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. This is true because a patch must not add functionality. 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 operating environment, but makes management of more complicated systems tedious.

As far as the packages that make up the Solaris operating environment 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 operating environment) needs to determine the patch level of a particular package that is part of the Solaris operating environment, 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
 
 
 
  Previous   Contents   Next