Graft - a package management utility

Prepared by Peter Samuel, Gormand Pty Ltd

$Revision: 2.4 $

$Date: 2002/02/25 20:32:14 $

graft: To insert (a graft) in a branch or stem of another tree; to propagate by insertion in another stock; also, to insert a graft upon. To implant a portion of (living flesh or skin) in a lesion so as to form an organic union. To join (one thing) to another as if by grafting, so as to bring about a close union.


Contents


Introduction

Graft provides a mechanism for managing multiple packages under a single directory hierarchy. It was inspired by both Depot (Carnegie Mellon University) and Stow (Bob Glickstein).

For the purposes of this discussion a package is defined as a suite of programs and files that make up an individual product. For example, the package known as gcc consists of the compiler and preprocessor programs, include files, manual pages and any other associated file or program. The concept of a package should not be confused with some vendor's definitions that are - by this definition - actually collections of packages.

Special thanks to Gordon Rowell, Charles Butcher, Charlie Brady and Robert Maldon for design suggestions and contributions.


Rationale

In any reasonably large environment, many software packages will be installed. The installation location for these packages usually follows one of three rationales - each with its own advantages and drawbacks:

  1. Each package is isolated from all other packages by installing it into a self contained directory tree. All binaries, manual pages, library and configuration files are stored under a single directory tree. This directory tree contains NO other files which are not the exclusive domain of the package in question.

    This method makes package demarcation obvious. As each package is self contained, identification of any file within a package is immediately apparent.

    Multiple versions of packages can be installed fairly easily to accommodate acceptance testing of new versions and/or legacy systems.

    However, the use of individual package directories can lead to VERY long $PATH and $MANPATH environment variables. Some shells may not be able to handle such long variables. Whenever a new package is added, each user MUST update their $PATH and $MANPATH to make the package available.

  2. Packages are installed under a common directory tree. Binaries for all packages are grouped in a single directory, manual pages for all packages in another directory and so on.

    This method eliminates the need for continually updating long $PATH variables for each user. As soon as a package is placed into the common 'bin' directory it is immediately available to all users (after a shell rehash if necessary).

    However, when a package is to be updated it is often very difficult to isolate all the files related to a particular package if they are intermingled with unrelated files.

  3. A combination of methods (1) and (2).

In an effort to maximise the advantages and minimise the disadvantages, Depot, Stow and Graft adopt a similar philosophy:

Packages are installed in self contained directory trees and symbolic links from a common area are made to the package files.

This approach allows multiple versions of the same package to co-exist on the one system. One version is the commonly available version and symbolic links will be made to this version. New versions can be tested and once acceptable can replace the current commonly available version. Older versions can still be used for legacy systems by using the 'real' path name to the package instead of the 'common' path name.

The size and complexity of environment variables such as $PATH and $MANPATH is minimised because only the common area is required. Any special cases can also be accommodated but these will usually be in the minority when compared with the number of commonly available packages.


Research

Note: Development of Graft began in late 1996. The comments regarding the packages listed below reflect their functionality and behaviour at that time and may not necessarily reflect their current functionality and behaviour.

As stated earlier, Graft was inspired by Depot and Stow. Both these systems were examined and finally rejected for the following reasons:

Depot

Depot is very flexible yet cumbersome.

It requires a database file to be created which provides a snapshot of the current state of both the package repository and the Depot target. It is possible to inadvertently destroy the package repository if the database is damaged.

Depot assumes "ownership" of the target area, making it almost impossible to accommodate packages that are not under the control of Depot. ("Ownership" in this case means that Depot assumes ALL files in the target area will be under the control of Depot. It does not imply that Depot modifies Unix file permissions).

Because of Depot's assumed ownership it is difficult for other packages not under the control of Depot to be placed in the same target area.

Depot attempts to impose a fixed package repository relative to the package target. It assumes that all packages will be stored under 'dir/depot/package' and the target will be 'dir'. This can be overridden on the command line but the internals of Depot make this mechanism cumbersome.

Depot is written in C and there are many source files in its distribution. Local modifications would be difficult to quickly implement and test.

Stow

Stow is a stateless system. It requires no database or configuration information.

Like Depot, it assumes that the package repository will be stored under 'dir/stow/package' and the target will be 'dir'. This can be overridden on the command line and works well during the install phase.

Stow assumes "ownership" of the target area, making it difficult to accommodate packages that are not under the control of Stow. ("Ownership" in this case means that Stow assumes ALL files in the target area will be under the control of Stow. It does not imply that Stow modifies Unix file permissions).

Because of Stow's assumed ownership it is difficult for other packages not under the control of Stow to be placed in the same target area. When deleting packages, Stow examines everything in the target directory - whether it is associated with the package it is trying to delete or not. This can be time consuming and potentially dangerous as empty directories are also removed - even empty directories that do not belong to the package being removed.

Stow has a clever feature of folding and unfolding directories. It attempts to optimise the number of symbolic links by making links to directories if the directory is only associated with a single package. If at a later date Stow discovers another package that needs that directory it will unfold that directory into a collection of symbolic links to files rather than a single symbolic link to the directory. Stow will fold the directory when removing packages if the remainder of the directory is only concerned with a single package. While clever, this feature is probably a waste of time and effort. It means that the entire package target must be scanned to determine package ownership of links and as packages will usually be replaced by newer versions a directory fold will probably be short lived.

Stow will sometimes miss potential conflicts when run in show only mode. The conflicts may occur when a directory is unfolded and will not show up in show only mode.

Stow's author suggests that packages be compiled such that they refer to files in the target location rather than the actual package installation directory. This approach precludes the use of multiple versions of packages with different configuration and/or library files.

Stow is written in Perl and is only a few hundred lines of code so local modifications can be accommodated. However there are very few comments in the code which makes the process of modification difficult.

Since the release of Graft 1.6, the existence of yet another packaging program has been brought to the author's attention.

Encap

Encap grew out of work begun at the University of Illinois Champaign-Urbana. It has the same underlying philosophy as Depot, Stow and Graft - encapsulate packages into self contained directories and use symbolic links to make them visible in a common location.

Encap uses a combination of a csh wrapper and a Perl program to accomplish its work. Like both Depot and Stow, Encap assumes that all compiled packages will live under a single directory hierarchy - by default 'dir/encap/package'. It then attempts to create a symbolic link tree for ALL the packages under this area. There doesn't appear to be any easy way to support the quick addition or removal of a single package.

A new release of Encap incorporating many new features was expected to be available in early 1997, however no release greater than version 1.2 has been forthcoming.

One good feature of Encap is the ability to exclude specific files from the package tree. This concept has been incorporated into Graft 1.7 and above.

Since the release of Graft 2.3, the existence of several another packaging programs have been brought to the author's attention. Rather than outline their features and whether or not the author feels they are superior (or inferior) to Graft, a reference to each package and a brief description is given and further research is left as an exercise for the reader:

stowES
http://freshmeat.net/projects/stowes/

"stowES (stow Enhancement Script) is a Perl script which tries to ease the use of the "stow" packaging program and software which can be compiled and installed with autoconf. It automates the compilation and installation of software packages and provides some useful functions to maintain your stow packages (e.g., list packages, check packages for integrity, etc.)."

opt_depot
http://www.arlut.utexas.edu/csd/opt_depot/

"opt_depot is a suite of Perl scripts which makes it easy to manage installed software across a wide range of client systems. opt_depot makes it possible to keep all files associated with a program together in one directory, so installation and de-installation is simple. opt_depot is easy to manage, and provides a scheme for installing software in a truly portable fashion; packages may be installed locally on client systems, or kept in a central package archive for NFS access. "

This site also has links to several other package management utilities, including Graft.

relink
http://sourceforge.net/projects/relink/

"relink is a package management tool for organization and management of software packages. It should run on any UNIX platform that runs PERL. Similar tools include: rpm(REDHAT/Mandrake), pkgadd(Slackware/SUN), stow(GNU) and depot(CMU)"

univSrcPkg
http://www.sistema.it/univSrcPkg/

Bud Bruegger has written a brief paper outlining his thoughts on a "Universal Source Package" solution.

This site also has links to other package management programs and similar items of interest. Note: Some of the links on this site are broken.


Design

This brings us to Graft. Graft has been designed to use the best features of Depot, Stow and Encap while maintaining as simple a mechanism as possible. The principles of Graft are:


Installation

Before installing Graft you'll need Perl 5.x. Graft version 2.x requires features only available with Perl 5.x and will not run with Perl 4.x.

Your operating system should also support symbolic links. If it doesn't then you can't use Graft! Graft will exit gracefully if your version of Perl does not support symbolic links. It will also exit gracefully if you attempt to graft a package into a file system that does not support symbolic links - from a Linux ext2 file system into an msdos file system for example.

Follow these instructions to install Graft:

  1. Unpack the gzipped Graft distribution:

        gunzip -c graft-2.4.tar.gz | tar xvf -
    
  2. change directories to the Graft distribution directory:

        cd graft-2.4
    
  3. Create an writable version of the Makefile by running the command

        make -f Makefile.dist
    

    You'll see output similar to

        cp Makefile.dist Makefile
        chmod 644 Makefile
    
        ######################################################
        #                                                    #
        #       You'll now need to modify the Makefile       #
        #      variables to suit your local conditions.      #
        #                                                    #
        ######################################################
    
        make: *** [Makefile] Error 1
    

    You can ignore the error message. That is just there to prevent the creation of the graft executable before you've made your site specific configurations to the Makefile.

  4. Edit the Makefile. The following variables should be modified to suit your local requirements:

    PACKAGEDIR	= /pkgs
    TARGETDIR	= /pkgs
    

    These two variables control your default package installation and target directories. Most sites will probably choose to install packages under a common installation directory and then graft them into a common target directory.

    If no specific target directory is given on the command line, Graft will use the default value specified by TARGETDIR. If a target directory is given on the command line but is not fully qualified, the value specified by TARGETDIR will be prepended to the command line argument.

    Package names provided to Graft that are not fully qualified will have the value specified by PACKAGEDIR prepended to the command line arguments.

    TOP		= $(PACKAGEDIR)/graft-$(VERSION)
    BIN		= $(TOP)/bin
    MAN		= $(TOP)/man
    DOC		= $(TOP)/doc
    

    There should be no need to modify these values unless you wish to install Graft into a directory that is different from your default package installation directory. If you do modify TOP you should not change the values of BIN, MAN and DOC. If you feel you must change these values then perhaps you've misunderstood the concept behind Graft so a re-read of this document may be in order.

    PERL		= /pkgs/bin/perl
    

    This variable refers to the location of the Perl 5.x that will be used by the Graft executable. If you plan on grafting Perl then this value should be the grafted location of Perl rather than the installation location of Perl. If you are using an operating system that comes with Perl 5.x - such as RedHat Linux - then you don't need to worry about grafting Perl so the value of PERL should reflect its installed location.

    BUILDPERL	= $(PERL)
    

    Perl is required during the make. You'll only need to change this if the current installed location of Perl is different to the future grafted location of Perl.

    LOGFILE		= /var/log/graft
    

    Graft logs all of its actions to a log file. Modify the value of LOGFILE to suit your local needs. An alternative name can be specified on the command line.

    If you want logging disabled by default, set the value of LOGFILE to /dev/null.

    GRAFT-IGNORE	= .nograft
    GRAFT-EXCLUDE	= .graft-exclude
    GRAFT-INCLUDE	= .graft-include
    

    These variables hold the names of the special Graft files that control whether or not subdirectories or files are grafted. If you change these values, try to choose obvious names. If you want the files to appear in a simple directory listing, do not use file names that begin with a dot "." character.

    GRAFT-NEVER	=
    

    This variable holds the names of the files and/or directories that should never be grafted. Typically these may be source code repositories as used by systems such as CVS, or perhaps lockfiles. The default value is empty but if you wish to specify values, simply add them to the variable using only whitespace as a separator. For example:

    GRAFT-NEVER	= CVS RCS SCCS .lock
    
    NEVERGRAFT	= 0
    

    If this variable is set to 1, the files and/or directories specified by GRAFT-NEVER will be automatically excluded from the grafted directory.

    If this variable is set to 0, the files and/or directories specified by GRAFT-NEVER will be not be excluded from the grafted directory.

    The sense of this value is reversed by use of the -C command line option.

    The automatic exclusion is bypassed completely if the grafted directory contains either a .nograft or .graft-include file.

    PRUNED-SUFFIX	= .pruned
    

    This variable sets the suffix name of pruned files. Pruned files will be renamed filename.pruned.

    SUPERUSER	= 1
    

    If this variable is set to 1 only the superuser can install, delete or prune packages. This can be overridden by a command line option. If this variable is set to 0, superuser privileges are not required and the override command line option is disabled.

    If you are installing a private copy of Graft to manage packages in your home directory you should set SUPERUSER to 0. If you're using Graft to manage a global set of packages you should set SUPERUSER to 1.

    PRESERVEPERMS	= 0
    

    When grafting packages, Graft will create new directories as required. By setting PRESERVEPERMS to 1, the original user id, group id and file modes will be carried over to the new directory. This variable is used only if SUPERUSER is set to 1. The sense of this variable can be reversed using a command line option.

    DELETEOBJECTS	= 0
    

    When deleting grafted packages, Graft may leave empty directories. Setting DELETEOBJECTS to 1 will allow Graft to delete these directories. If DELETEOBJECTS is 0 then Graft will display an appropriate message reminding the user that a directory has been emptied. The sense of this variable can be reversed using a command line option.

    It's probably not good practise to set this value to 1 as some directories may be used as place holders by a number of different packages. If the value is set to 0 deletion of directories can be forced via a command line option.

    When pruning packages, graft can either remove conflicting files or rename them. If DELETEOBJECTS is set to 1 the default prune action will be to delete conflicting objects. If DELETEOBJECTS is set to 0 the default prune action will be to rename conflicting objects. The sense of this variable can be reversed using a command line option.

    Save your changes and exit from the editor.

  5. Remove any existing executables by running:

        make clean
    

    You should see output similar to:

        rm -f graft
    
  6. Create the Graft executable by running:

        make
    

    You should see output similar to:

        /pkgs/bin/perl -c graft.pl
        graft.pl syntax OK
        sed                                             \
    	-e 's#xDELETEOBJECTSx#0#g'                  \
    	-e 's#xGRAFT-EXCLUDEx#.graft-exclude#g'     \
    	-e 's#xGRAFT-IGNOREx#.nograft#g'            \
    	-e 's#xGRAFT-INCLUDEx#.graft-include#g'     \
    	-e 's#xLOGFILEx#/var/log/graft#g'           \
    	-e 's#xPACKAGEDIRx#/pkgs#g'                 \
    	-e 's#xPERLx#/pkgs/bin/perl#g'              \
    	-e 's#xPRESERVEPERMSx#0#g'                  \
    	-e 's#xPRUNED-SUFFIXx#.pruned#g'            \
    	-e 's#xSUPERUSERx#1#g'                      \
    	-e 's#xTARGETDIRx#/pkgs#g'                  \
    	< graft.pl > graft
        chmod +x graft
    
  7. If you're using the automounter under Solaris 2.x, the installation process may not be able to directly create the directory specified by TOP. If this is the case then manually create this directory using whatever procedures are appropriate for your operating system.

    For example, if the /pkgs mount point is under the control of the automounter via an entry in the auto_pkgs map:

        *	nfshost:/export/sparc-SunOS-5.5.1/pkgs/&
    

    you'll need to create the Graft installation directory by executing the following command on the machine nfshost:

        mkdir /export/sparc-SunOS-5.5.1/pkgs/graft-2.4
    
  8. Install the Graft executable, manual page and documentation by executing:

        make install
    

    You should see output similar to:

    cp graft /pkgs/graft-2.4/bin
    for i in graft.1;                                               \
    do                                                              \
        manpage=`basename $i`;                                      \
        man=`expr $i : '.*\.\(.\)'`;                                \
        mkdir -p /pkgs/graft-2.4/man/man$man;                       \
        sed                                                         \
            -e 's#xDELETEOBJECTSx#0#g'                              \
            -e 's#xGRAFT-EXCLUDEx#.graft-exclude#g'                 \
            -e 's#xGRAFT-IGNOREx#.nograft#g'                        \
            -e 's#xGRAFT-INCLUDEx#.graft-include#g'                 \
            -e 's#xLOGFILEx#/var/log/graft#g'                       \
            -e 's#xPACKAGEDIRx#/pkgs#g'                             \
            -e 's#xPRESERVEPERMSx#0#g'                              \
            -e 's#xPRUNED-SUFFIXx#.pruned#g'                        \
            -e 's#xSUPERUSERx#1#g'                                  \
            -e 's#xTARGETDIRx#/pkgs#g'                              \
            -e 's#xVERSIONx#2.4#g'                                  \
            < $i > /pkgs/graft-2.4/man/man$man/$manpage;      \
        chmod 644 /pkgs/graft-2.4/man/man$man/$manpage;             \
    done
    for i in graft.html graft.pdf graft.ps graft.txt; \
    do                                      \
        mkdir -p /pkgs/graft-2.4/doc;       \
        cp doc/$i /pkgs/graft-2.4/doc;      \
        chmod 644 /pkgs/graft-2.4/doc/$i;   \
        touch /pkgs/graft-2.4/doc/.nograft; \
    done
    

Graft is now installed and ready to be used.

NOTE: If you make changes to your Graft installation at a later date, please run the following commands:

    make clean
    make install

Failure to do this may result in a Graft manual page that does NOT reflect your current configuration.


Grafting Graft and Perl - the bootstrap problem

If you are using an operating system that comes with Perl 5.x - such as RedHat Linux - then you don't need to worry about grafting Perl, so some of this section can be ignored.

Embedded into the Graft executable is the location of the Perl executable. If you've understood the concept behind Graft then this location may be the grafted location of Perl rather than the true location of Perl.

This presents a dilemma when you come to graft both Graft and Perl. You can't run the grafted location of the Graft executable because it doesn't exist yet, and you can't run the real location of the Graft executable because Perl hasn't been grafted yet.

Assuming that Graft and Perl are installed in

    /pkgs/graft-2.4
    /pkgs/perl-5.6.0

you can resolve this dilemma by executing the following commands:

    /pkgs/perl-5.6.0/bin/perl /pkgs/graft-2.4/bin/graft -i graft-2.4
    /pkgs/perl-5.6.0/bin/perl /pkgs/graft-2.4/bin/graft -i perl-5.6.0

This will graft both Graft and Perl from the default package installation directory (as specified by PACKAGEDIR in the Makefile) into your default target directory (as specified by TARGETDIR in the Makefile).

If you don't wish to use the default directories you can use the following commands instead:

    /pkgs/perl-5.6.0/bin/perl /pkgs/graft-2.4/bin/graft -i -t /pkgs /pkgs/graft-2.4
    /pkgs/perl-5.6.0/bin/perl /pkgs/graft-2.4/bin/graft -i -t /pkgs /pkgs/perl-5.6.0

Now both Graft and Perl have been grafted and any other package can be grafted by executing the simpler command:

    graft -i package

The Graft 2.4 distribution includes a program called graftBootStrap.sh which allows you to easily graft both Graft and Perl. It can be found in the contrib directory of the distribution.


Using Graft

Compiling Packages

Any packages you wish to place under the control of Graft should be compiled and installed in such a way that any package dependent files are referenced with the ACTUAL package installation directory rather than the common area in which Graft will be creating symbolic links. For example, ensure that Perl version 5.6.0 is looking for its library files in /pkgs/perl-5.6.0/lib/perl5 instead of /pkgs/lib/perl5. This approach will allow you to easily separate multiple versions of the same package without any problems.


Graft command line options

All of the details concerning actions, package locations and target directories are passed to Graft on the command line. (Graft 1.x used a configuration file. This has now been deprecated in favour of a log file).

Graft's command line options can be summarised as:

    graft -i [-P|u] [-l log] [-n] [-v|V] [-s|-t target] package package ...
    graft -d [-D] [-u] [-l log] [-n] [-v|V] [-s|-t target] package package ...
    graft -p [-D] [-u] [-l log] [-n] [-v|V] [-s|-t target] package package ...

Graft has three basic actions:

  1. Install

        graft -i [-C] [-P|u] [-l log] [-n] [-v|V] [-s|-t target] package package ...
    

    -i

    Install symbolic links from the package installation directory to the target directory. Requires superuser privileges if SUPERUSER was set to 1 in the Makefile.

    -C

    If NEVERGRAFT was set to 1 in the Makefile, disable the automatic exclusion of files and/or directories whose names exactly match the values specified by GRAFT-NEVER in the Makefile.

    If NEVERGRAFT was set to 0 in the Makefile, force the automatic exclusion of files and/or directories whose names exactly match the values specified by GRAFT-NEVER in the Makefile.

    Can only be used with the -i option.

    This option is ignored for each grafted directory, if the directory contains a .nograft or .graft-include file.

    The Graft manual page will correctly reflect the status of this option based on the values specified in the Makefile.

    -P

    Preserve modes and ownerships when creating new directories if PRESERVEPERMS was set to 0 in the Makefile. Do not preserve modes and ownerships if the option is not provided on the command line.

    Do not preserve modes and ownerships when creating new directories if PRESERVEPERMS was set to 1 in the Makefile. Preserve modes and ownerships if the option is not provided on the command line.

    Cannot be used with the -u option.

    This options does not apply if SUPERUSER was set to 0 in the Makefile.

    The Graft manual page will correctly reflect the status of this option based on the values specified in the Makefile.

    -u

    Superuser privileges are not required when installing packages.

    Cannot be used with the -P option.

    This option is only available if SUPERUSER was set to 1 in the Makefile.

    The Graft manual page will correctly reflect the status of this option based on the values specified in the Makefile.

    -l log

    Specify an alternate log file instead of the default specified by LOGFILE in the Makefile. No logging is performed if the -n option is used.

    Log entries have the form:

        878790215   1.10+   I    /pkgs/cpio-2.4.2      /pkgs
        878888916   2.1     I    /pkgs/gzip-1.2.4      /pkgs
        878888916   2.1     IC   /pkgs/gzip-1.2.4/bin/gzip   invalid symlink
    

    This shows that a development version of graft (1.10+) was used to install symbolic links from /pkgs/cpio-2.4.2 to /pkgs. A new version of graft (2.1) was used to install symbolic links from /pkgs/gzip-1.2.4 to /pkgs. The IC entry indicates that a conflict occurred during this installation - the file /pkgs/bin/gzip was a symbolic link to something other than /pkgs/gzip-1.2.4/bin/gzip.

    -n

    List actions but do not perform them. Implies the very verbose option. Does not require superuser privileges regardless of the value of SUPERUSER in the Makefile.

    -v

    Be verbose.

    -V

    Be very verbose.

    -s

    Stow/Depot compatibility mode. Infer the Graft target directory from each package installation directory in the manner of Stow and Depot.

    Target directory is the dirname of the dirname of the package installation directory. (Yes that really is two dirnames). So if the package installation directory is

        /usr/local/depot/gzip-1.2.4
    

    the package will be grafted into

        /usr/local
    

    Cannot be used with the -t option.

    -t target

    Override the default graft target directory with target. The value of target must be a fully qualified directory and it must exist.

    Cannot be used with the -s option.

    package

    Install the named package. If package is a fully qualified directory, use it as the package installation directory. If package is not a fully qualified directory, prepend it with the value of PACKAGEDIR as specified in the Makefile.


  2. Delete

        graft -d [-D] [-u] [-l log] [-n] [-v|V] [-s|-t target] package package ...
    

    -d

    Delete symbolic links from the package target directory to the package installation directory. Requires superuser privileges if SUPERUSER was set to 1 in the Makefile.

    -D

    Delete empty directories if DELETEOBJECTS was set to 0 in the Makefile. If the option is not provided on the command line, notify the user that a directory has been emptied.

    Do not delete empty directories if DELETEOBJECTS was set to 1 in the Makefile. Notify the user that a directory has been emptied. If the option is not provided on the command line, delete empty directories.

    The Graft manual page will correctly reflect the status of this option based on the values specified in the Makefile.

    -u

    Superuser privileges are not required when deleting packages.

    This option is only available if SUPERUSER was set to 1 in the Makefile.

    The Graft manual page will correctly reflect the status of this option based on the values specified in the Makefile.

    -l log

    Specify an alternate log file instead of the default specified by LOGFILE in the Makefile. No logging is performed if the -n option is used.

    Log entries have the form:

        879126278       1.10+   D       /pkgs/weblint-1.017     /pkgs
        879126278       1.10+   DC      /pkgs/weblint-1.017/bin/weblint  file exists
        879126278       1.10+   DC      /pkgs/weblint-1.017/man/man1/weblint.1  file exists
    

    This shows that a development version of graft (1.10+) was used to delete symbolic links from /pkgs to /pkgs/weblint-1.017. The DC entries indicate that conflicts occurred during this action - the files /pkgs/bin/weblint and /pkgs/man/man1/weblint.1 already exist.

    -n

    List actions but do not perform them. Implies the very verbose option. Does not require superuser privileges regardless of the value of SUPERUSER in the Makefile.

    -v

    Be verbose.

    -V

    Be very verbose.

    -s

    Stow/Depot compatibility mode. Infer the Graft target directory from each package installation directory in the manner of Stow and Depot.

    Target directory is the dirname of the dirname of the package installation directory. (Yes that really is two dirnames). So if the package installation directory is

        /usr/local/depot/gzip-1.2.4
    

    the package will be grafted into

        /usr/local
    

    Cannot be used with the -t option.

    -t target

    Override the default graft target directory with target. The value of target must be a fully qualified directory and it must exist.

    Cannot be used with the -s option.

    package

    Delete the named package. If package is a fully qualified directory, use it as the package installation directory. If package is not a fully qualified directory, prepend it with the value of PACKAGEDIR as specified in the Makefile.


  3. Prune

        graft -p [-D] [-u] [-l log] [-n] [-v|V] [-s|-t target] package package ...
    

    -p

    Prune objects (files, links or directories) from the package target directory that are in conflict with the package installation directory. Requires superuser privileges if SUPERUSER was set to 1 in the Makefile.

    -D

    Remove conflicting objects if DELETEOBJECTS was set to 0 in the Makefile. Rename conflicting objects as object.pruned if the option is not provided on the command line.

    Rename conflicting objects to object.pruned if DELETEOBJECTS was set to 1 in the Makefile. Remove conflicting objects if the option is not provided in the command line.

    If a directory is to be removed and it is not empty, it will be renamed as dir.pruned and a suitable warning message will be given regardless of the sense of this flag.

    The Graft manual page will correctly reflect the status of this option based on the values specified in the Makefile.

    -u

    Superuser privileges are not required when pruning packages.

    This option is only available if SUPERUSER was set to 1 in the Makefile.

    The Graft manual page will correctly reflect the status of this option based on the values specified in the Makefile.

    -l log

    Specify an alternate log file instead of the default specified by LOGFILE in the Makefile. No logging is performed if the -n option is used.

    Log entries have the form:

        879126283       1.10+   P       /pkgs/weblint-1.017     /pkgs
    

    This shows that a development version of graft (1.10+) was used to delete objects from /pkgs that were in conflict with /pkgs/weblint-1.017.

    -n

    List actions but do not perform them. Implies the very verbose option. Does not require superuser privileges regardless of the value of SUPERUSER in the Makefile.

    -v

    Be verbose.

    -V

    Be very verbose.

    -s

    Stow/Depot compatibility mode. Infer the Graft target directory from each package installation directory in the manner of Stow and Depot.

    Target directory is the dirname of the dirname of the package installation directory. (Yes that really is two dirnames). So if the package installation directory is

        /usr/local/depot/gzip-1.2.4
    

    the package will be grafted into

        /usr/local
    

    Cannot be used with the -t option.

    -t target

    Override the default graft target directory with target. The value of target must be a fully qualified directory and it must exist.

    Cannot be used with the -s option.

    package

    Prune the named package. If package is a fully qualified directory, use it as the package installation directory. If package is not a fully qualified directory, prepend it with the value of PACKAGEDIR as specified in the Makefile.


Testing the Graft Installation

Before creating the symbolic links from the target directory to the package directory, you may wish to see what actions Graft will perform. Execute the following command:

    graft -i -n package-name

The -i option tells Graft to install the package and the -n option tells Graft to report on its actions without actually performing them. The default Graft target directory will be used and the package installation directory will be taken from the fully qualified package argument or the default value will be prepended to the package argument if it is not fully qualified.

Graft will report on the following actions:

If you were to test the installation of the kermit-5A190 package you would execute the command:

    graft -i -n kermit-5A190
You should see output resembling:
    Installing   links to /pkgs/kermit-5A190 in /pkgs
    Processing   /pkgs/kermit-5A190
    SYMLINK      /pkgs/README -> /pkgs/kermit-5A190/README
    NOP          /pkgs/kermit-5A190/bin and /pkgs/bin are both directories
    Processing   /pkgs/kermit-5A190/bin
    SYMLINK      /pkgs/bin/kermit -> /pkgs/kermit-5A190/bin/kermit
    SYMLINK      /pkgs/bin/wart -> /pkgs/kermit-5A190/bin/wart
    NOP          /pkgs/kermit-5A190/man and /pkgs/man are both directories
    Processing   /pkgs/kermit-5A190/man
    NOP          /pkgs/kermit-5A190/man/man1 and /pkgs/man/man1 are both directories
    Processing   /pkgs/kermit-5A190/man/man1
    SYMLINK      /pkgs/man/man1/kermit.1 -> /pkgs/kermit-5A190/man/man1/kermit.1
    MKDIR        /pkgs/doc
    Processing   /pkgs/kermit-5A190/doc
    SYMLINK      /pkgs/doc/ckccfg.doc -> /pkgs/kermit-5A190/doc/ckccfg.doc
    SYMLINK      /pkgs/doc/ckuins.doc -> /pkgs/kermit-5A190/doc/ckuins.doc
    SYMLINK      /pkgs/doc/ckc190.upd -> /pkgs/kermit-5A190/doc/ckc190.upd
    SYMLINK      /pkgs/doc/ckcker.upd -> /pkgs/kermit-5A190/doc/ckcker.upd
    SYMLINK      /pkgs/doc/ckaaaa.hlp -> /pkgs/kermit-5A190/doc/ckaaaa.hlp
    SYMLINK      /pkgs/doc/ckuaaa.hlp -> /pkgs/kermit-5A190/doc/ckuaaa.hlp
    NOP          /pkgs/kermit-5A190/lib and /pkgs/lib are both directories
    Processing   /pkgs/kermit-5A190/lib
    SYMLINK      /pkgs/lib/ckedemo.ini -> /pkgs/kermit-5A190/lib/ckedemo.ini
    SYMLINK      /pkgs/lib/ckeracu.ini -> /pkgs/kermit-5A190/lib/ckeracu.ini
    SYMLINK      /pkgs/lib/ckermit.ini -> /pkgs/kermit-5A190/lib/ckermit.ini
    SYMLINK      /pkgs/lib/ckermod.ini -> /pkgs/kermit-5A190/lib/ckermod.ini
    SYMLINK      /pkgs/lib/cketest.ini -> /pkgs/kermit-5A190/lib/cketest.ini
    SYMLINK      /pkgs/lib/ckevt.ini -> /pkgs/kermit-5A190/lib/ckevt.ini
    SYMLINK      /pkgs/lib/ckurzsz.ini -> /pkgs/kermit-5A190/lib/ckurzsz.ini

This output shows you that most of the directories already exist (indicated by the NOP flags). A symbolic link will be created in the relevant target directory to each of the files in the kermit-5A190 package. One directory exists in the kermit-5A190 package that does not exist in the target - doc. This directory will be created by Graft.

NOTE: If you are using the automounter you may not be able to create the directory /pkgs/doc. You'll have to create the directory on the NFS server under the file system in which it really lives. You should be familiar with the peculiarities of the automounter and your specific site configuration before creating any directories directly under mount points used by the automounter.


Installing Packages

Once you have ensured that Graft will perform the correct actions, you can execute:

    graft -i package-name

So to install kermit you would execute:

    graft -i kermit-5A190

There will be no output from Graft unless it encounters a conflict. If you wish to see more information you can specify one of the verbose flags. For a minimum of output you can execute:

    graft -i -v kermit-5A190

You should see the following output:

    Processing   /pkgs/kermit-5A190
    Processing   /pkgs/kermit-5A190/bin
    Processing   /pkgs/kermit-5A190/man
    Processing   /pkgs/kermit-5A190/man/man1
    Processing   /pkgs/kermit-5A190/doc
    Processing   /pkgs/kermit-5A190/lib

If you choose the very verbose option by executing:

    graft -i -V kermit-5A190

the output will be the same as that when the -n option was used, however this time Graft will actually create the symbolic links.

    Installing   links to /pkgs/kermit-5A190 in /pkgs
    Processing   /pkgs/kermit-5A190
    SYMLINK      /pkgs/README -> /pkgs/kermit-5A190/README
    NOP          /pkgs/kermit-5A190/bin and /pkgs/bin are both directories
    Processing   /pkgs/kermit-5A190/bin
    SYMLINK      /pkgs/bin/kermit -> /pkgs/kermit-5A190/bin/kermit
    SYMLINK      /pkgs/bin/wart -> /pkgs/kermit-5A190/bin/wart
    NOP          /pkgs/kermit-5A190/man and /pkgs/man are both directories
    Processing   /pkgs/kermit-5A190/man
    NOP          /pkgs/kermit-5A190/man/man1 and /pkgs/man/man1 are both directories
    Processing   /pkgs/kermit-5A190/man/man1
    SYMLINK      /pkgs/man/man1/kermit.1 -> /pkgs/kermit-5A190/man/man1/kermit.1
    NOP          /pkgs/kermit-5A190/doc and /pkgs/doc are both directories
    Processing   /pkgs/kermit-5A190/doc
    SYMLINK      /pkgs/doc/ckccfg.doc -> /pkgs/kermit-5A190/doc/ckccfg.doc
    SYMLINK      /pkgs/doc/ckuins.doc -> /pkgs/kermit-5A190/doc/ckuins.doc
    SYMLINK      /pkgs/doc/ckc190.upd -> /pkgs/kermit-5A190/doc/ckc190.upd
    SYMLINK      /pkgs/doc/ckcker.upd -> /pkgs/kermit-5A190/doc/ckcker.upd
    SYMLINK      /pkgs/doc/ckaaaa.hlp -> /pkgs/kermit-5A190/doc/ckaaaa.hlp
    SYMLINK      /pkgs/doc/ckuaaa.hlp -> /pkgs/kermit-5A190/doc/ckuaaa.hlp
    NOP          /pkgs/kermit-5A190/lib and /pkgs/lib are both directories
    Processing   /pkgs/kermit-5A190/lib
    SYMLINK      /pkgs/lib/ckedemo.ini -> /pkgs/kermit-5A190/lib/ckedemo.ini
    SYMLINK      /pkgs/lib/ckeracu.ini -> /pkgs/kermit-5A190/lib/ckeracu.ini
    SYMLINK      /pkgs/lib/ckermit.ini -> /pkgs/kermit-5A190/lib/ckermit.ini
    SYMLINK      /pkgs/lib/ckermod.ini -> /pkgs/kermit-5A190/lib/ckermod.ini
    SYMLINK      /pkgs/lib/cketest.ini -> /pkgs/kermit-5A190/lib/cketest.ini
    SYMLINK      /pkgs/lib/ckevt.ini -> /pkgs/kermit-5A190/lib/ckevt.ini
    SYMLINK      /pkgs/lib/ckurzsz.ini -> /pkgs/kermit-5A190/lib/ckurzsz.ini

NOTE: In this case the /pkgs/doc directory was not created by Graft because /pkgs is a mount point controlled by the automounter. The doc directory was created manually prior to executing Graft.


Bypassing package directories

You may have the need to place only part of a package under the control of Graft. Examples of such occasions may be:

You can force Graft to bypass a directory by creating the file

    package-name/dir/dir/.nograft

Using the second example above, if you were to create the file:

    /pkgs/perl-5.6.0/lib/perl5/.nograft

Graft would create directories and symbolic links for every file and directory down to /pkgs/perl-5.6.0/lib. The perl5 directory and anything below it would not be created.


Including specific files and/or directories

There may be the occasional need to include specific files and/or directories in a directory, rather than the entire directory tree itself. An example of such an occurrence would be the case where a package contains a number of subdirectories, only one of which is required to be grafted.

You can force Graft to only include any number of files and/or directories in a package directory by creating the file

    .graft-include

in the same directory.

.graft-include will contain a list of file and/or directory names - one per line - of the files and/or directories you wish to include.

Consider the a2ps package for example. When installed it contains the following directories:

    /pkgs/a2ps-4.13b/bin
    /pkgs/a2ps-4.13b/etc
    /pkgs/a2ps-4.13b/include
    /pkgs/a2ps-4.13b/info
    /pkgs/a2ps-4.13b/lib
    /pkgs/a2ps-4.13b/man
    /pkgs/a2ps-4.13b/share

The only directory you wish to graft is the bin directory. You could place a .nograft file in each of the other directories, OR you could create a single .graft-include file in /pkgs/a2ps-4.13b/.graft-include. This file would contain

    bin

Now only the bin directory will be grafted.


Excluding specific files and/or directories

There may be the occasional need to exclude specific files and/or directories from a directory, rather than the entire directory itself. An example of such an occurrence would be the case where files from different packages have the same name. Emacs and Xemacs use the same names for a number of their configuration files for example.

You can force Graft to exclude any number of files and/or directories from a package directory by creating the file

    .graft-exclude

in the same directory.

.graft-exclude will contain a list of file and/or directory names - one per line - of the files and/or directories you wish to exclude.

For example, if you did not wish the file

    /pkgs/sudo-1.5.3/etc/sudoers

to be grafted as

    /pkgs/etc/sudoers

but you did want

    /pkgs/sudo-1.5.3/etc/visudo

to be grafted as

    /pkgs/etc/visudo

you would create the file

    /pkgs/sudo-1.5.3/etc/.graft-exclude

and ensure its contents contained the line:

    sudoers

NOTE: Any entries made in a .graft-exclude file will override the same entries made in a .graft-include file. That is, if a file or directory name is listen in both a .graft-exclude and a .graft-include file, it will be excluded from the graft.


Grafting part of a package

Some packages can be successfully used when only part of their installation directory is grafted. Other packages are recalcitrant and need some special hand holding which can only be solved by grafting each section of the package separately.

The first scenario can be handled by either .nograft files or partial grafts. Consider Perl version 5.6.0. When installed in its own directory

    /pkgs/perl-5.6.0

there are three subdirectories

    drwxr-sr-x   2 psamuel  bisg         512 Oct 30  1996 bin
    drwxr-sr-x   3 psamuel  bisg         512 Oct 30  1996 lib
    drwxr-sr-x   4 psamuel  bisg         512 Oct 30  1996 man

Everything in the lib directory is exclusive to Perl and does not require grafting. Therefore, perl-5.6.0 can be grafted using either of the following two methods:

    touch /pkgs/perl-5.6.0/lib/.nograft
    graft -i perl-5.6.0
or
    graft -it /pkgs/bin perl-5.6.0/bin
    graft -it /pkgs/man perl-5.6.0/man

Now let's consider a recalcitrant package - ObjectStore version 4.0.2.a.0. When installed in

    /pkgs/ostore-4.0.2.a.0

the following files and directories are available:

    -rwxrwxr-x   1 pauln    one3        1089 Oct 31  1996 Copyright
    drwxrwxrwx   8 pauln    one3         512 Oct  2  1996 common
    drwxrwxrwx   6 pauln    one3         512 Oct 31  1996 sunpro
    -rw-r-----   1 root     one3     1900544 Apr 29  1997 txn.log

The executable programs that need to be grafted are in sunpro/bin and the manual pages that need to be grafted are in common/man. Everything else in the package does not need to be grafted. If the entire package was to be grafted the result would be two directories that are not in the regular $PATH and $MANPATH environment variables - namely /pkgs/common/man and /pkgs/sunpro/bin, plus a host of other directories that are not relevant for grafting. No amount of .nograft and .graft-exclude juggling will solve this problem.

The solution is to use two partial grafts:

    graft -it /pkgs/bin ostore-4.0.2.a.0/sunpro/bin
    graft -it /pkgs/man ostore-4.0.2.a.0/common/bin

Using this approach, the correct executables and manual pages are available without the need to graft unnecessary files and directories.


Deleting and/or Upgrading Packages

If you wish to upgrade a package - let's assume you wish to upgrade kermit from version 5A190 to version 6.0.192 - you'd follow these steps.

Firstly, you'd compile and install kermit-6.0.192 in

    /pkgs/kermit-6.0.192

Once you'd tested it to your satisfaction, you'd need to delete the symbolic links to the current grafted version. You can check which actions Graft will perform by executing:

    graft -d -n kermit-5A190

You'll see output similar to

    Uninstalling links from /pkgs to /pkgs/kermit-5A190
    Processing   /pkgs/kermit-5A190
    Processing   /pkgs/kermit-5A190/bin
    UNLINK       /pkgs/bin/kermit
    UNLINK       /pkgs/bin/wart
    Processing   /pkgs/kermit-5A190/man
    Processing   /pkgs/kermit-5A190/man/man1
    UNLINK       /pkgs/man/man1/kermit.1
    Processing   /pkgs/kermit-5A190/doc
    UNLINK       /pkgs/doc/ckccfg.doc
    UNLINK       /pkgs/doc/ckuins.doc
    UNLINK       /pkgs/doc/ckc190.upd
    UNLINK       /pkgs/doc/ckcker.upd
    UNLINK       /pkgs/doc/ckaaaa.hlp
    UNLINK       /pkgs/doc/ckuaaa.hlp
    Processing   /pkgs/kermit-5A190/lib
    UNLINK       /pkgs/lib/ckedemo.ini
    UNLINK       /pkgs/lib/ckeracu.ini
    UNLINK       /pkgs/lib/ckermit.ini
    UNLINK       /pkgs/lib/ckermod.ini
    UNLINK       /pkgs/lib/cketest.ini
    UNLINK       /pkgs/lib/ckevt.ini
    UNLINK       /pkgs/lib/ckurzsz.ini
    UNLINK       /pkgs/lib/.testing

If you're happy with the output from the test deletion you can delete the grafted package. Once again, you'll only see output if a failure occurs unless you use one of the verbose options.

If you execute:

    graft -dV kermit-5A190

you'll see:

    Uninstalling links from /pkgs to /pkgs/kermit-5A190
    Processing   /pkgs/kermit-5A190
    Processing   /pkgs/kermit-5A190/bin
    UNLINK       /pkgs/bin/kermit
    UNLINK       /pkgs/bin/wart
    Processing   /pkgs/kermit-5A190/man
    Processing   /pkgs/kermit-5A190/man/man1
    UNLINK       /pkgs/man/man1/kermit.1
    Processing   /pkgs/kermit-5A190/doc
    UNLINK       /pkgs/doc/ckccfg.doc
    UNLINK       /pkgs/doc/ckuins.doc
    UNLINK       /pkgs/doc/ckc190.upd
    UNLINK       /pkgs/doc/ckcker.upd
    UNLINK       /pkgs/doc/ckaaaa.hlp
    UNLINK       /pkgs/doc/ckuaaa.hlp
    EMPTY        /pkgs/doc is now empty. Delete manually if necessary.
    Processing   /pkgs/kermit-5A190/lib
    UNLINK       /pkgs/lib/ckedemo.ini
    UNLINK       /pkgs/lib/ckeracu.ini
    UNLINK       /pkgs/lib/ckermit.ini
    UNLINK       /pkgs/lib/ckermod.ini
    UNLINK       /pkgs/lib/cketest.ini
    UNLINK       /pkgs/lib/ckevt.ini
    UNLINK       /pkgs/lib/ckurzsz.ini

NOTE: In this case the existence of an empty directory has been discovered. If Graft empties a directory during a package deletion, it will either notify you or delete the directory depending on the combination of variables in the Makefile and command line options. It's probably better practise not to automatically delete empty directories as they may be used by other packages - such as lock file directories for example.

Now you can remove the real package contents. (You may not wish to do this immediately as some legacy systems may depend on features provided by the older version or you may feel the need for further testing before feeling confident that the old version can be removed):

    rm -rf /pkgs/kermit-5A190

Now you can graft the new version of kermit. Execute:

    graft -i -n kermit-6.0.192

to ensure that the grafting will proceed without error. Once you are satisfied that this is the case you can graft the new package by executing:

    graft -i kermit-6.0.192

Transitioning a package to Graft control

Graft can be used to easily transition a package from its current installation in your target directory to a grafted installation.

As an example, let's consider the package weblint version 1.017. It consists of three files installed in:

    /usr/local/bin/weblint
    /usr/local/lib/global.weblintrc
    /usr/local/man/man1/weblint.1

The first step is to create a new copy of the package in its own directory:

    /pkgs/weblint-1.017

Ensure that any references to library files are now made to /pkgs/weblint-1.017/lib instead of /usr/local/lib.

Test the new installation to ensure it behaves as expected.

Then prune the old files from /usr/local/* using:

    graft -pVt /usr/local weblint-1.017

You'd expect to see output similar to:

    Pruning      files in /usr/local which conflict with /pkgs/weblint-1.017
    Processing   /pkgs/weblint-1.017
    Processing   /pkgs/weblint-1.017/man
    Processing   /pkgs/weblint-1.017/man/man1
    RENAME       /usr/local/man/man1/weblint.1
    Processing   /pkgs/weblint-1.017/bin
    RENAME       /usr/local/bin/weblint
    Processing   /pkgs/weblint-1.017/lib
    RENAME       /usr/local/lib/global.weblintrc

If you elected to delete conflicting files instead of renaming them you'd use:

    graft -pDVt /usr/local weblint-1.017

and you'd see output similar to:

    Pruning      files in /usr/local which conflict with /pkgs/weblint-1.017
    Processing   /pkgs/weblint-1.017
    Processing   /pkgs/weblint-1.017/man
    Processing   /pkgs/weblint-1.017/man/man1
    UNLINK       /usr/local/man/man1/weblint.1
    Processing   /pkgs/weblint-1.017/bin
    UNLINK       /usr/local/bin/weblint
    Processing   /pkgs/weblint-1.017/lib
    UNLINK       /usr/local/lib/global.weblintrc

Now the new version of weblint 1.017 can be grafted in place:

    graft -it /usr/local weblint-1.017

The grafted version of weblint can now be tested.

If we renamed conflicting files, they can be removed once the grafted weblint has been satisfactorily tested:

    rm /usr/local/man/man1/weblint.1.pruned
    rm /usr/local/bin/weblint.pruned
    rm /usr/local/lib/global.weblintrc.pruned

Conflict Processing

Occasionally Graft will fail to completely install a package. This occurs because Graft encounters a conflict. A conflict is defined as one of the following possibilities:

Package Object Target Object
directory not a directory
file directory
file file
file symbolic link to something other than the package object

If Graft encounters such a conflict during the installation of a package it will report the conflict and exit.

Resolving the conflict depends on the nature of the conflict and is beyond the scope of this discussion - however most conflicts will either be the result of attempting to graft a package on top of the same package actually installed in the target directory or a file name clash between two (or more) different packages.

Conflicts arising from the pre-existence of a package in the target directory can be resolved using graft's prune mechanism described above in "Transitioning a package to Graft control".

File name clash conflicts can be resolved by the use of either a .nograft or .graft-exclude file or by grafting only part of a package as described above in "Grafting part of a package".

If Graft encounters a conflict while deleting a package, it will report the conflict and continue deleting the remainder of the package. In this way Graft will delete as much of the package as possible. Conflicts that arise during deletion will probably be the result of an incorrectly installed package or the installation of other components of the same package without the use of Graft.

Conflict messages are written to standard error. All other messages are written to standard output. To quickly determine if a package will have any conflicts when grafted, redirect standard output to standard error:

    graft -i -n package > /dev/null

If you don't see any output then you can safely assume that there will be no conflicts when grafting this package.


Exit Status

Graft will terminate with an exit status of either 0, 1, 2 or 3 under the following conditions:

Exit Status Condition
0
All operations succeeded.
1
A conflict occurred during installation.
2
Command line syntax was incorrect.
3
One or more packages listed on the command line does not exist. Other valid packages listed on the command line were processed correctly.


Using Graft with other package management tools

Many Unix vendors are releasing their own package management tools with their operating systems. Examples of this are Solaris 2.x with its SVR4 Package Manager pkgadd, RedHat Linux with its RedHat Package Manager rpm and HP-UX 10.x with its swinstall suite. Graft has been designed as an adjunct to these package managers rather than a competitor.


Availability

The latest version of Graft should always be available from:

    http://www.gormand.com.au/peters/tools/

License

Graft is licensed under the terms of the GNU General Public License, Version 2, June 1991.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA, or download it from the Free Software Foundation's web site:

    http://www.gnu.org/copyleft/gpl.html
    http://www.gnu.org/copyleft/gpl.txt