#! /bin/sh # # Copyright (c) 2003-2004 Silicon Graphics, Inc. All Rights Reserved. # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 2 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # 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., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # Process a gendist-like idb file to collect, install, upgrade # and remove files. # # The format of the IDB file is: # # The first line must contain a version string "# Version 1" # # All following lines must contain: # d|f|h|s mode owner group destination source option # # One of: # d directory to be created at destination (source ignored) # f file to be installed from source to destination # h hard link to be created at destination to source # s soft link to be created at destination to source # # mode four digit mode bits # owner file's owner # group file's group # destination create file/directory/link here # source where file came from, or what link points to # # One of these options: replace, update, suggest, check or noupdate. # # Optionally, a os version number (uname -r), which ensures the file will only # get installed on that os version, or a minimum and a maximum version number (inclusive). # # If the target does not exist, all options just install the file. # # If the target already exists, the options do the following: # # Option Type Operation # --------- ---- --------------------------------------------------- # replace d update permissions # fhs remove and install replacement # # update d hs illegal option # f move existing file to file.bak, install new file # # suggest d hs illegal option # f install new file as file.new # # check dfhs do nothing # # noupdate dfhs do nothing # # # If removing the target, the target is removed unless the option is # noupdate. Directories will not be removed if they are not empty. # # Lines starting with '#' or empty lines are ignored. # tmp=`mktemp -d /tmp/pcp.XXXXXXXXX` || exit 1 prog=`basename $0` status=1 result=0 # non-fatal errors will set this to 1 now=`date` host=`hostname` dump_mesg=false trap "rm -rf $tmp; exit \$status" 0 1 2 15 MKDIR=/bin/mkdir CP=/bin/cp MV=/bin/mv LN=/bin/ln RM=/bin/rm RMDIR=/bin/rmdir CHMOD=/bin/chmod CHOWN=/usr/sbin/chown CHGRP=/usr/bin/chgrp DIRNAME=/usr/bin/dirname TEE=/usr/bin/tee JOIN=/usr/bin/join SED=/usr/bin/sed AWK=/usr/bin/awk SORT=/usr/bin/sort UNAME=/usr/bin/uname OS_VERSION=`$UNAME -r` _usage() { cat << EOF Usage: $prog [-cinpr] [-C os_version] [-l logfile] [-u origidb] [-R root] idbfile -l logfile Save important output to logfile -n Show actions but do not execute -R root Collect or install files using root rather than WORKAREA or / -v Verbose output -C os_ver (Only used for collect phase) Only collect files which will be used for os_ver One of: -c Collect source files into current dir -i Install files from current directory -p Parse and check idb is valid -r Remove installed files. -u origidb Remove files not in new IDB and then install EOF exit 1 } _echo() { if $log then echo "$*" | $TEE -a $logfile else echo "$*" fi } _error() { _echo "$prog: Error: $*" | $TEE -a $mesg dump_mesg=true result=1 exit 1 } _warn() { _echo "$prog: Warning: $*" | $TEE -a $mesg dump_mesg=true } _debug() { sed -e "s/^/$prog: Debug: /" | $TEE -a $mesg dump_mesg=true } _mesg_head() { _echo "$*" | $TEE -a $mesg } _mesg() { _echo "$prog: $*" | $TEE -a $mesg dump_mesg=true } _note() { _echo "$prog: Note: $*" } _cmd() { $verbose && _echo "$*" $* } _get_numeric_version() { echo $1 | awk ' { n = split($0, v, "."); if (n == 2) v[2] = 0; print (v[0] * 100) + (v[1] * 10) + v[2]; } ' } _test_version() { local _min_numeric local _max_numeric local _os_numeric local _min_version=$1 local _max_version=$2 local _temp_numeric if [ -z "$_min_version" -o "$_min_version" = "$OS_VERSION" ]; then # No version, or exact match return 0 elif [ -z "$_max_version" ]; then # No max and no exact match with min return 1 else # max and min without exact match, so test range _min_numeric=`_get_numeric_version $_min_version` _max_numeric=`_get_numeric_version $_max_version` _os_numeric=`_get_numeric_version $OS_VERSION` if [ $_min_numeric -gt $_max_numeric ]; then _temp_numeric=$_min_numeric _min_numeric=$_max_numeric _max_numeric=$_temp_numeric fi if [ $_os_numeric -ge $_min_numeric -a $_os_numeric -le $_max_numeric ]; then return 0 else return 1 fi fi } _remove_file() { if _test_version $2 $3 then if [ -f $root/$1 ] then if ! _cmd $RM $root/$1 then _warn "Unable to remove $root/$1, continuing" fi else _note "$root/$1 does not exist for removal, skipping" fi fi } _remove_dir() { if _test_version $2 $3 then if [ -d $root/$1 ] then if ! _cmd $RMDIR $root/$1 then _warn "Unable to remove $root/$1, continuing" fi else _note "$root/$1 does not exist for removal, skipping" fi fi } # $1 is path to file in tree # $2 is path to the file in the workarea # path is created in current directory and the file is copied # _collect_file() { file=$root/$1 dir=`$DIRNAME $2` if [ ! -z "$collect_version" ]; then # override the OS_VERSION, which is used in test_version() OS_VERSION=$collect_version if ! _test_version $3 $4 then # version doesn't match, so skip it return fi fi if [ ! -r $file ] then _error "Unable to find $file" fi if [ ! -d $dir ] then if _cmd $MKDIR -p $dir then : else _error "Unable to create $dir" fi fi if ! _cmd $CP $file $2 then _error "Unable to copy $file" fi } # Takes the idb line # d|f|h|s mode owner group destination source option _install_file() { type=$1 target=$root/$5 source=$6 option=$7 chopts= if _test_version $8 $9 then # Set to true if we need to set mode, uid and gid installed=false if [ $type = "d" ] then if [ ! -d $target ] then if _cmd $MKDIR -p $target then installed=true else _error "Unable to create dir $target" fi elif [ -f $target ] then if [ $option = "replace" ] then if ! _cmd $RM $target then _error "Unable to replace existing $target with directory" fi if _cmd $MKDIR -p $target then installed=true else _error "Unable to create dir $target" fi else _error "$target exists but is not a directory" fi fi elif [ $type = "f" ] then if [ ! -f $source ] then echo "cwd=`pwd`" >$tmp/debug echo "source dir=`dirname $source`" >>$tmp/debug ls -l `dirname $source` >>$tmp/debug _debug <$tmp/debug _error "Unable to find file ($source) for $target" fi if [ -f $target ] then case $option in replace) if ! _cmd $RM $target then _warn "Unable to remove existing $target, continuing" fi if _cmd $MV $source $target then installed=true else _error "Unable to install $target" fi ;; update) if ! _cmd $CP $target $target.bak then _warn "Unable to copy existing $target to $target.bak, continuing" fi if ! _cmd $RM $target then _warn "Unable to remove existing $target, continuing" fi if _cmd $MV $source $target then _mesg "Update to $target installed, original backed up at $target.bak" installed=true else if _cmd $CP $target.bak $target then _error "Unable to install $target, original moved back" else _error "Unable to install $target, original now $target.bak" fi fi ;; suggest) if _cmd $MV $source $target.new then _mesg "Suggested update to $target installed at $target.new" installed=true target=$target.new else _warn "Unable to install $target as $target.new, continuing" fi ;; check) # Nothing to do if file exists ;; noupdate) # Nothing to do if file exists ;; *) _error "Unsupported IDB option ($option) for $target" ;; esac elif [ -d $target ] then _error "$target exists but is a directory" else if _cmd $MV $source $target then installed=true else _error "Unable to install $target" fi fi elif [ $type = "h" ] then if [ -f $target ] then case $option in replace) if ! _cmd $RM $target then _warn "Unable to remove existing $target, continuing" fi if _cmd $LN $root/$source $target then installed=true else _error "Unable to install $target" fi ;; update) _error "Illegal update option for hard link $target to $root/$source" ;; suggest) _error "Illegal suggest option for hard link $target to $root/$source" ;; check) # Nothing to do if file exists ;; noupdate) # Nothing to do if file exists ;; esac elif [ -d $target ] then _error "$target exists but is a directory" else if _cmd $LN $root/$source $target then installed=true else _error "Unable to install $target" fi fi elif [ $type = "s" ] then chopts="-h" if [ -f $target ] then case $option in replace) if ! _cmd $RM $target then _warn "Unable to remove existing $target, continuing" fi if _cmd $LN -s -f $root/$source $target then installed=true else _error "Unable to install $target" fi ;; update) _error "Illegal update option for soft link $target to $root/$source" ;; suggest) _error "Illegal suggest option for soft link $target to $root/$source" ;; check) # Nothing to do if file exists ;; noupdate) # Nothing to do if file exists ;; esac elif [ -d $target ] then _error "$target exists but is a directory" else if _cmd $LN -s -f $root/$source $target then installed=true else _error "Unable to install $target" fi fi else _error "Unrecognised file type ($type)" fi if $installed then if [ "$type" != "s" ] ; then if ! _cmd $CHMOD $2 $target then _warn "Unable to change mode of $target to $2, continuing" fi fi if ! _cmd $CHOWN $chopts $3 $target then _warn "Unable to change ownership of $target to $3, continuing" fi if ! _cmd $CHGRP $chopts $4 $target then _warn "Unable to change group of $target to $4, continuing" fi fi fi } _remove() { # # Directories are removed after all files are removed # If directory is not empty, default is to ignore and move on # sort -k1r,1 -k5r,5 $1 < $1 \ | $AWK >> $tmp/cmds ' $7 == "noupdate" { next } $1 == "d" { printf("_remove_dir %s %s %s\n", $5, $8, $9); next } { printf("_remove_file %s %s %s\n", $5, $8, $9) } ' } _install() { # # Directories are installed before all files are installed # Links are installed after normal files # sed < $tmp/idb >> $tmp/cmds -e 's/^/_install_file /' } show=false collect=false install=false remove=false upgrade=false verbose=false logfile= log=false mesg=$tmp/mesg root= collect_version= parse=false if set -- `getopt cC:il:nprR:u:v $*` then for i in $* do case $i in -C) $install && _usage $remove && _usage $upgrade && _usage $parse && _usage collect=true collect_version=$2 echo "Collecting for $collect_version" shift 2;; -c) $install && _usage $remove && _usage $upgrade && _usage $parse && _usage collect=true shift;; -i) $collect && _usage $remove && _usage $upgrade && _usage $parse && _usage install=true shift;; -l) log=true logfile=$2 touch $logfile if [ ! -w $logfile ] then log=false _warn "Unable to open logfile ($logfile)" fi shift 2;; -n) show=true shift;; -p) $collect && _usage $install && _usage $upgrade && _usage $remove && _usage parse=true shift;; -r) $collect && _usage $install && _usage $upgrade && _usage $parse && _usage remove=true shift;; -R) root=$2 shift 2;; -u) $collect && _usage $install && _usage $remove && _usage $parse && _usage upgrade=true oidb=$2 shift 2;; -v) verbose=true shift;; --) break shift;; esac done shift else _usage fi if [ $# -ne 1 ] then _usage fi idb=$1 if [ ! -r $idb ] then echo "$prog: Unable to open $idb" exit 1 fi $AWK < $idb > $tmp/err -v prog=$prog -v idb=$idb ' BEGIN { fail = 0; version = 0; entries = 0 } NR == 1 && ( NF != 3 || $1 != "#" || $2 != "Version" || $3 != "1" ) { printf("%s: Error: Line %d of %s has an invalid version string (%s)\n", prog, NR, idb, $0); fail = 1; exit(1); } NR == 1 { version = 1; next } /^#/ { next } NF == 0 { next } NF != 7 && NF != 8 && NF != 9 { printf("%s: Error: Line %d of %s does not have 7 or 8 or 9 entries (%d)\n", prog, NR, idb, NF); fail = 1; exit(1); } $1 != "d" && $1 != "f" && $1 != "h" && $1 != "s" { printf("%s: Error: Line %d of %s has invalid file type (%s)\n", prog, NR, idb, $1); fail = 1; exit(1); } $7 != "replace" && $7 != "update" && $7 != "suggest" && $7 != "check" && $7 != "noupdate" { printf("%s: Error: Line %d of %s has invalid file option (%s)\n", prog, NR, idb, $7); fail = 1; exit(1); } $1 != "f" && ($7 == "update" || $7 == "suggest") { printf("%s: Error: Line %d of %s has unsupported combination of %s and %s\n", prog, NR, idb, $1, $7); fail = 1; exit(1); } $5 ~ "^/" { printf("%s: Error: Line %d of %s does not have relative path (%s)\n", prog, NR, idb, $5); fail = 1; exit(1); } $6 ~ "^/" { printf("%s: Error: Line %d of %s does not have relative path (%s)\n", prog, NR, idb, $6); fail = 1; exit(1); } { entries++ } END { if (!fail) { if (!version) { printf("%s: Error: No version string in %s\n", prog, idb); exit(1); } if (!entries) { printf("%s: Error: No entries in %s\n", prog, idb); exit(1); } } } ' if [ -s $tmp/err ] then echo "$prog: Invalid IDB file detected:" cat $tmp/err exit 1 elif $parse then status=0 exit fi $RM -f $tmp/dirs $tmp/cmds $tmp/files $tmp/idb $tmp/tidb touch $tmp/dirs $tmp/cmds $tmp/files sed < $idb -e '/^#/d' -e '/^$/d' \ | $SORT -k1,1 -k5,5 \ > $tmp/idb cat << EOF > $mesg ======================================================================= Installation issues that may require further investigation: EOF if $remove then _mesg_head "Removing all installed PCP files" _mesg_head "Date: "`date` _mesg_head "User: $USER" _remove $tmp/idb # # Collect files out of source tree and copy here using the same # directory structure # elif $collect then _mesg_head "Collecting all files to be included in package" _mesg_head "Date: "`date` _mesg_head "User: $USER" [ "X$root" = "X" ] && root=$WORKAREA if [ "X$root" = "X" ] then _error "Neither \$WORKAREA or -R were set" exit 1 fi $AWK < $tmp/idb > $tmp/cmds ' $1 == "f" { printf("_collect_file %s %s %s %s\n", $5, $6, $8, $9) }' # # Install files assuming that they do not already exist # elif $install then _mesg_head "Installing PCP files" _mesg_head "Date: "`date` _mesg_head "User: $USER" _install elif $upgrade then _mesg_head "Upgrade of PCP files" _mesg_head "Date: "`date` _mesg_head "User: $USER" # Determine which files need to be removed since they # are not in the new set of files. Files which are # in both sets will be upgraded depending on their # IDB option. if [ ! -r $oidb ] then _warn "Upgrade in progress but cannot find old inventory ($oidb) to delete files that are no longer required" else sed < $oidb -e '/^#/d' -e '/^$/d' \ | $SORT -k1,1 -k5,5 \ > $tmp/oidb # explicity name all fields so that the extra os_version field doesnt confuse join $JOIN -t' ' -v 2 -1 5 -2 5 $tmp/idb $tmp/oidb > $tmp/extra if [ -s $tmp/extra ] then _remove $tmp/extra else _note "No files need to be removed before upgrade" fi fi _install else echo "$prog: Must select one of -r, -c, -i or -u options" exit 1 fi if $show then cat $tmp/cmds else $verbose && cat $tmp/cmds . $tmp/cmds fi if $dump_mesg then $log && cat $mesg >> $logfile cat $mesg fi status=$result