#!/bin/sh
# protopkg - Slackware .tgz Packaging Tool
#
# Written by David Cantrell, licensed under the GPL (any version).
# Copyright 2000, BSDi, Concord, California, USA.
# All rights reserved.
#
# Portions were taken from the "hdsetup" tools in Slackware Linux:
#
# Copyright 1994, 1998  Patrick Volkerding, Moorhead, Minnesota USA
# All rights reserved.
#
# Redistribution and use of this script, with or without modification, is
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
#  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
#  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

#
# Standard Defines
#

# version number
VER="2.0"

# extension for Slackware packages
PKG_EXTENSION=tgz

# package rules file name
PKG_RULES=rules

# the default editor that protopkg uses to verify the package listing
EDITOR=/usr/bin/vi

# general temp directory
if [ "$TMP" = "" ]
then
   TMP=/tmp
fi

#
# afterinstall()
# Called by protopkg() after the install function from the package
# prototype file is run.  Generates a listing of all files on the system,
# compares against what beforeinstall() found and from that generates a
# list of files that go in the package.  Neat, huh?
#

afterinstall() {
   cd $TMP
   find / $IPATH -printf "%p - %T@ - %s\n" > $TMP/after.lst
   cat before.lst after.lst | sort | uniq -u > $TMP/package.lst

   # in the case that the destination stuff existed, eliminate duplicates
   cat package.lst | cut -d' ' -f1 | uniq | 
                     grep -v "usr/info/dir" | 
                     grep -v usr/local/info/dir > $TMP/package.new
   rm -f package.lst ; mv package.new package.lst

   # remove the before and after lists, since we don't need them anymore
   rm -rf $TMP/before.lst
   rm -rf $TMP/after.lst
}

#
# beforeinstall()
# Called by protopkg() before the install function from the package
# prototype file is run.  Makes a list of files on the system that
# afterinstall() will read later.
#

beforeinstall() {
   rm -f $TMP/before.lst
   find / $IPATH -printf "%p - %T@ - %s\n" > $TMP/before.lst
}

#
# build_package()
# Called by protopkg() after afterinstall() has been run.  It takes the
# list of files that go in the package and tars them up.  It then moves to
# the temporary package build directory and untars those files there.
# This is done so that permissions and such can be fixed up on the package
# contents.
#
# Parameters:   $1    The package name
#               $2    The package tree
#

build_package() {
   cd $TMP
   mkdir -p $2
   chown root.root $2
   chmod 755 $2

   for hejaz in `cat $TMP/package.lst`
   do
      if [ ! "`file $hejaz | grep \"symbolic link\"`" = "" ]
      then
         # we want symbolic links
         echo $hejaz >> $TMP/archive.lst
      elif [ ! "`file $hejaz | grep \"directory\$\"`" = "" -a ! -f $hejaz ]
      then
         # empty directories get caught for later creation
         echo $hejaz >> $TMP/emptydirs.lst
      elif [ ! -d $hejaz ]
      then
         # catch all other files
         echo $hejaz >> $TMP/archive.lst
      fi
   done

   rm -rf $TMP/package.lst

   # create the package tree
   cd $2
   if [ "$VERBOSE" = "y" ]
   then
      tar -cvf - -T $TMP/archive.lst | tar -xf -
   else
      tar -cvf - -T $TMP/archive.lst | tar -xf - 1>/dev/null 2>/dev/null
   fi

   # run through our directory list and make that in the tree
   for d in `cat $TMP/emptydirs.lst`
   do
      tmpD=`trim_slash $d`
      if [ ! -d $PKG/$tmpD ]
      then
         mkdir -p $PKG/$tmpD
      fi
   done

   rm -rf $TMP/emptydirs.lst
   rm -rf $TMP/archive.lst
}

#
# compressdocs()
# Compresses all man pages and info files in a package tree.
#
# Parameters:   $1    The package tree.
#

compressdocs() {
   dir1=`pwd`
   cd $1
   for dn in usr \
             usr/local \
             'opt/*' \
             usr/X11R6 \
             usr/openwin
   do
      # adapted by Jasper Huijsmans to repair broken links
      # and to also gzip the originally installed file
      for dn2 in man share/man info share/info
      do
         for file in `find $dn/$dn2 -type f 2>/dev/null`
         do
            gzip -9f $1/$file /$file 2>/dev/null
         done
         for link in `find $dn/$dn2 -type l 2>/dev/null`
         do
            relink_docs $1/$link 2>/dev/null
            relink_docs /$link 2>/dev/null
         done
      done
   done
   cd $dir1
}

#
# doinst_rules()
# Adds doinst.sh code for removing additional control files, installing
# info files, and handling the config file "rules" file.
#
# Parameters:    $1      The package tree.
#                $2      The control directory in the package tree.
#

doinst_rules() {
   # handle any info files that are in the package tree
   cd $1
   noIf=y

   for dn in usr \
             usr/local \
             opt \
             usr/X11R6 \
             usr/openwin
   do
      # find unique info files in the directory
      for infof in `find $1/$dn/info -type f -print 2>/dev/null | \
                    grep -v -e '.*-[0-9]\+\.gz'`
      do
         if [ "$noIf" = "y" ]
         then
            ( cd $1/install
              echo "# Install the info files for this package" >> doinst.sh
              echo "if [ -x usr/bin/install-info ]" >> doinst.sh
              echo "then" >> doinst.sh )
            noIf=n
         fi

         echo "   usr/bin/install-info --info-dir=/$dn/info /$dn/info/`basename $infof` 2>/dev/null" >> $1/install/doinst.sh
      done
   done

   # close the installer block
   if [ "$noIf" = "n" ]
   then
      ( cd $1/install
        echo "fi" >> doinst.sh
        echo >> doinst.sh )
   fi

   # handle any config files in the config file processing dir
   if [ -d $1/install/conf ]
   then
      ( cd $1/install
        echo "# Process the incoming configuration files" >> doinst.sh )

      cat $CWD/rules | read_rules "conf" | (
         while read Caction Cfile
         do
            if [ "$Cfile" = "" ]
            then
               ### This is the default installation method
               realF=`trim_slash $Caction`

               ( cd $1/install
                 echo >> doinst.sh
                 cat << EOF >> doinst.sh
if [ ! -f $realF ]
then
   mv install/conf/$realF $realF
else
   if [ "\`md5sum install/conf/$realF | cut -d ' ' -f 1 2>/dev/null\`" = "\`md5sum $realF | cut -d ' ' -f 1 2>/dev/null\`" ]
   then
      mv install/conf/$realF $realF
   else
      mv install/conf/$realF $realF.new
   fi
fi
EOF
               )
            elif [ "$Caction" = "ONE" ]
            then
               ### Handle the ONE installation mode
               realF=`trim_slash $Cfile`

               ( cd $1/install
                 echo >> doinst.sh
                 cat << EOF >> doinst.sh
if [ ! -f $realF ]
then
   mv install/conf/$realF $realF
else
   if [ "\`md5sum install/conf/$realF | cut -d ' ' -f 1 2>/dev/null\`" = "\`md5sum $realF | cut -d ' ' -f 1 2>/dev/null\`" ]
   then
      mv install/conf/$realF $realF
   else
      rm install/conf/$realF
   fi
fi
EOF
               )
            else
               ### We need to handle the other modes
               realF=`trim_slash $Cfile`

               if [ "$Caction" = "NEW" ]
               then
                  destF="$realF.new"
               elif [ "$Caction" = "EXAMPLE" ]
               then
                  destF="$realF.example"
               elif [ "$Caction" = "OW" ]
               then
                  destF=$realF
               fi

               ( cd $1/install
                 echo >> doinst.sh
                 echo "mv install/conf/$realF $destF" >> doinst.sh )
            fi
         done )

      ( cd $1/install
        echo >> doinst.sh
        echo "# Make sure the incoming conf directory is gone" >> doinst.sh
        echo "rm -rf install/conf" >> doinst.sh )
   fi
}

#
# download_sources() - If the source code has not been downloaded and we have
# a sources file, try to fetch the source.  If the source is downloaded and
# can be verified, do that and exit.
#
# Parameters:   $1    The name of the sources file.
#

download_sources() {
   # find a suitable download tool
   if [ -x `which wget` ]
   then
      DWNLDTOOL="`which wget`"
   elif [ -x `which lynx` ]
   then
      DWNLDTOOL="`which lynx` -source"
   else
      echo
      echo "You lack a sufficient download tool for protopkg.  Make sure"
      echo "that either wget or lynx is installed, both of which are in"
      echo "Slackware in the networking series."
      echo
      exit
   fi

   # let's do it
   cat $1 | read_rules "sources" | (
      while read Surl Smd5
      do
         Sfile="`basename $Surl`"
         GOOD="n"

         # do we have the file already?
         if [ -f $Sfile ]
         then
            # we have the file, does it check out?
            if [ ! "$Smd5" = "" ]
            then
               oldSum="`md5sum $Sfile`"
               oldSum="`echo $oldSum | cut -d ' ' -f 1`"

               if [ "$oldSum" = "$Smd5" ]
               then
                  GOOD="y"
               else
                  GOOD="n"
               fi
            else
               # we can't verify the MD5 sig, assume good
               GOOD="y"
            fi
         fi

         # let's fetch
         if [ "$GOOD" = "n" ]
         then
            $DWNLDTOOL $Surl

            if [ ! "$Smd5" = "" ]
            then
               # verify the MD5 sig
               newSum="`md5sum $Sfile`"
               newSum="`echo $newSum | cut -d ' ' -f 1`"

               if [ ! "$newSum" = "$Smd5" ]
               then
                  echo "$Sfile is invalid!"
                  mv $Sfile $Sfile.invalid
               fi
            fi
         fi
      done )

   unset Surl Smd5 Sfile GOOD
}

#
# filesize()
# This function duplicates what the "filesize" program in Slackware does.
# We may not have this on the particular filesystem we're on.  It's
# simple enough to just be a function.
#
# The argument it accepts is a filename to take the size of.
#

file_size() {
   SIZE=`ls -l -d -G "$1" | cut -b23-32`
   echo -n $SIZE
}

#
# fixate_package()
# Called by protopkg() to actually create the final package.  It packages
# up the contents of the temporary package build directory.
#
# Parameters:   $1   The package name.
#               $2   The package tree.
#               $3   The embedded package control directory.
#

fixate_package() {
   cd $2

   # first, warn for any zero-length files that may be unintentional
   find . -type f -size 0c | while read file
   do
      echo "WARNING: zero length file $file"
   done

   find . -type f -name '*.gz' -size 20c | while read file
   do
      echo "WARNING: possible empty gzipped file $file"
   done

   # Add more useful stuff to the doinst.sh script
   doinst_rules $2 $3

   # if the control directory is still empty, remove it.  --lj
   rmdir $3 1>/dev/null 2>/dev/null

   # now, create the actual package
   if [ "$VERBOSE" = "y" ]
   then
      echo
      tar -cvf $CWD/$1.tar .
      gzip -9 $CWD/$1.tar
      mv $CWD/$1.tar.gz $CWD/$1.tgz
   else
      tar -cvf $CWD/$1.tar . 2>/dev/null 1>/dev/null
      gzip -9 $CWD/$1.tar 2>/dev/null 1>/dev/null
      mv $CWD/$1.tar.gz $CWD/$1.tgz 2>/dev/null 1>/dev/null
   fi

   # Generate a package_descriptions file if the user wants that
   if [ "$CREATE_DESCRIPTION" = "y" -a ! "$DESC" = "" ]
   then
      echo >> $CWD/package_descriptions

      # output the package name first
      for a in 1 2 3 4 5 6 7 8 9 10 11
      do
         echo "$PKGNAME:" >> $TMP/leftside
      done

      # now place the description in a file
      echo -e "$DESC" > $TMP/rightside

      # combine the two
      paste -d ' ' $TMP/leftside $TMP/rightside >> $CWD/package_descriptions
      echo >> $CWD/package_descriptions
      rm -rf $TMP/leftside $TMP/rightside
   fi
}

#
# make_install_script()
# Runs through the temporary package directory and searches for symlinks.
# These are put into a script to create those symlinks at installation
# time.
#

make_install_script() {
   COUNT=1
   LINE="`sed -n "$COUNT p" $1`"
   while [ ! "$LINE" = "" ]
   do
      LINKGOESIN="`echo "$LINE" | cut -f 1 -d " "`" 
      LINKGOESIN="`dirname $LINKGOESIN`" 
      LINKNAMEIS="`echo "$LINE" | cut -f 1 -d ' '`"
      LINKNAMEIS="`basename "$LINKNAMEIS"`"
      LINKPOINTSTO="`echo "$LINE" | cut -f 3 -d ' '`"
      echo "( cd $LINKGOESIN ; rm -rf $LINKNAMEIS )"
      echo "( cd $LINKGOESIN ; ln -sf $LINKPOINTSTO $LINKNAMEIS )"
      COUNT=`expr $COUNT + 1`
      LINE="`sed -n "$COUNT p" $1`"
   done
}

#
# permissionize()
# Called by protopkg() to fix permissions on the temporary package build
# directory.  It sets all bin and sbin stuff to root.bin, chmod'ed 755.
# After that it runs the ownerships and permissions functions from the
# package prototype file (to set any special permissions the package
# maintainer wants).
#
# Parameters:   $1    The package tree.
#

permissionize() {
   cd $1

   if [ ! "$SETATTR" = "n" ]
   then
      # default system-wide permissions
      find . -exec chown root.root {} \; 1>/dev/null 2>/dev/null
      find . -type d -exec chmod 755 {} \; 1>/dev/null 2>/dev/null
      find . -type d -exec chown root.root {} \; 1>/dev/null 2>/dev/null

      # default bin and sbin permissions (root.bin, 755)
      for d in usr/bin usr/sbin usr/X11R6/bin bin sbin usr/local/bin usr/local/sbin
      do
         chown -R root.bin $1/$d 2>/dev/null
         chmod -R 755 $1/$d 2>/dev/null
      done

      # default include permissions
      find usr/include -name *.h -exec chmod 644 {} \; 1>/dev/null 2>/dev/null
      find usr/X11R6/include -name *.h -exec chmod 644 {} \; 1>/dev/null 2>/dev/null

      # default man and info page permissions
      find usr/man -type f -exec chmod 644 {} \; 1>/dev/null 2>/dev/null
      find usr/X11R6/man -type f -exec chmod 644 {} \; 1>/dev/null 2>/dev/null
      find usr/info -type f -exec chmod 644 {} \; 1>/dev/null 2>/dev/null

      # default /usr/doc permissions and ownerships
      find . | grep "usr/doc" | xargs chown root.root 1>/dev/null 2>/dev/null
      find . -type f | grep "usr/doc" | xargs chmod 644 1>/dev/null 2>/dev/null
      find . -type d | grep "usr/doc" | xargs chmod 755 1>/dev/null 2>/dev/null
   fi

   # run the user-defined functions
   if [ ! "$SUBPKGNAME" = "" ]
   then
      ### we're in a subpackage, a la repack()
      subattributes 2>/dev/null 1>/dev/null
   else
      ### looks like a regular ol' package to me
      attributes 2>/dev/null 1>/dev/null
   fi
}

#
# prepare_conf()
# Called by protopkg to move the config files specified in the rules file
# to the CONF processing directory.
#
# Parameters:     $1    The subpackage name (leave empty for main package)
#

prepare_conf() {
   # get the full path to the rules file
   if [ "$1" = "" ]
   then
      rulesFile="$CWD/rules"
      confDir="$CTL/conf"
      pkgRoot="$PKG"
   else
      rulesFile="$CWD/rules.$1"
      confDir="$SUBCTL/conf"
      pkgRoot="$SUBPKG"
   fi

   # let's do it
   if [ -f $rulesFile ]
   then
      cat $rulesFile | read_rules "conf" | (
         while read Caction Cfile
         do
            # get the file to put in the conf processing dir
            if [ "$Cfile" = "" ]
            then
               copyFile=$Caction
            else
               copyFile=$Cfile
            fi

            # move the file
            echo "   --> $copyFile"
            mkdir -p "$confDir/`dirname $copyFile`"
            mv $pkgRoot/$copyFile $confDir/$copyFile
         done )

      unset Caction Cfile pkgRoot copyFile confDir
   fi
}

#
# read_rules
# Reads the specified rules file for the specified block.
#
# Parameters:    $1      The block name to search for.
#

read_rules() {
   sed -e '/^#/d' -e '/^$/d' | sed -n "/^${1}:/,/^:${1}$/p" | 
   grep -Fv "${1}:" | grep -Fv ":${1}" | sed -e "s/^[ \t]*//"
}

#
# relink_docs()
# repair broken links to docs cause by gzipping
# added by Jasper Huijsmans
#
# Parameters:  $1    The symbolic link file
#

relink_docs() {
   LINKGOESIN=`dirname $1`
   LINKNAMEIS=`basename $1`
   LINKPOINTSTO=`ls -l $1 | cut -b58- | cut -f 3 -d ' '`
   rm $1
   # Here comes the trick: just add .gz to every name
   ( cd $LINKGOESIN; ln -s $LINKPOINTSTO.gz $LINKNAMEIS.gz )
}

#
# repack()
# This is a function called by prototype files when one source tree
# builds multiple binary packages.
#

repack() {
   # $1 is our subpackage name to create

   if [ "$1" = "" ]
   then
      echo "ERROR:  Subpackage name not specified for repack() call."
   else
      ### the way repack works is similar to protopkg.  it inherits certain
      ### pieces of information from the main prototype, but has it's own
      ### as well.  it's kinda hard to follow (especially at 5:43AM), so
      ### pay close attention.   -David

      # look for a subproto
      if [ ! -f $CWD/prototype.$1 ]
      then
         echo "ERROR:  Aborting repack(), prototype.$1 not found."
         return
      else
         . $CWD/prototype.$1
      fi

      echo
      echo "Building subpackage $1..."
      echo

      ### VERIFY SUBPROTOTYPE FILE
      echo -n "Verifying subpackage prototype file..."
      verifyproto "sub" "$1"
      echo "good, building package [$SUBPKGNAME]"

      ### FETCH SOURCE CODE IF WE NEED TO
      if [ -f $CWD/sources.$1 ]
      then
         echo -n "Fetching source code..."
         cd $CWD
         if [ "$VERBOSE" = "y" ]
         then
            echo
            download_sources $CWD/sources.$1
         else
            download_sources $CWD/sources.$1 2>/dev/null 1>/dev/null
            echo "done."
         fi
      fi

      ### DO WE NEED TO COMPILE?
      if [ ! "$COMPILED" = "y" ]
      then
         echo -n "Compiling package [$PKGNAME]..."
         cd $TMP
         if [ "$VERBOSE" = "y" ]
         then
            echo
            compile
         else
            compile 2>/dev/null 1>/dev/null
            echo "done."
         fi
         COMPILED=y
      fi

      ### BUILD LIST OF INSTALLED FILES
      echo -n "Generating list of files for subpackage [$SUBPKGNAME]..."
      beforeinstall
      if [ "$VERBOSE" = "y" ]
      then
         echo
         subinstall
      else
         subinstall 2>/dev/null 1>/dev/null
      fi
      afterinstall
      if [ ! "$VERBOSE" = "y" ]
      then
         echo "done."
      fi

      ### HAVE USER PLAY WITH PACKAGE LIST UNLESS BATCH MODE IS SET
      if [ ! "$BATCH" = "y" ]
      then
         cat << EOF

protopkg has gathered a list of what it things should be in the subpackage.
You will now be given the opportunity to edit that list if you see that
protopkg missed anything or got something wrong.  Just type in the editor
you want to use and hit enter to edit the package list.  After you exit
the editor, protopkg will finish making the subpackage.

EOF
         echo -n "Editor to use [default: $EDITOR]: "
         read usered

         if [ "$usered" = "" ]
         then
            $EDITOR $TMP/package.lst
         else
            if [ ! -x $usered -a ! -f $usered ]
            then
               echo
               echo "Error with editor specified, defaulting to $EDITOR"
               $EDITOR $TMP/package.lst
            else
               $usered $TMP/package.lst
            fi
         fi
      fi
      echo

      ### SET THE SUBPACKAGE AND SUBPACKAGE CONTROL VARIABLES
      SUBPKG=$TMP/pkg-$SUBPKGNAME
      SUBCTL=$SUBPKG/install

      if [ "$VERBOSE" = "y" ]
      then
         echo "Creating subpackage tree..."
         build_package $SUBPKGNAME $SUBPKG
         stripeverything $SUBPKG
         restoredoctimes $SUBPKGNAME $SUBPKG
         permissionize $SUBPKG
         compressdocs $SUBPKG
      else
         echo -n "Creating subpackage tree..."
         build_package $SUBPKGNAME $SUBPKG 2>/dev/null 1>/dev/null
         stripeverything $SUBPKG 2>/dev/null 1>/dev/null
         restoredoctimes $SUBPKGNAME $SUBPKG 2>/dev/null 1>/dev/null
         permissionize $SUBPKG 2>/dev/null 1>/dev/null
         compressdocs $SUBPKG 2>/dev/null 1>/dev/null
      fi

      if [ ! "$VERBOSE" = "y" ]
      then
         echo "done."
      fi

      ### CHECK FOR ZERO-LENGTH FILES
      if [ "$VERBOSE" = "y" ]
      then
         echo "Checking for zero-length files..."
      fi
      zerocheck $SUBPKG

      ### MAKE THE SYMLINKS FOR THE DOINST
      if [ "$VERBOSE" = "y" ]
      then
         symlinks $SUBPKG $SUBCTL
      else
         symlinks $SUBPKG $SUBCTL 2>/dev/null 1>/dev/null
      fi

      ### RUN THE SPECIAL FUNCTION
      cd $SUBPKG
      if [ "$VERBOSE" = "y" ]
      then
         subspecial
      else
         subspecial 2>/dev/null 1>/dev/null
      fi

      ### READ THE RULES FILE AND PREPARE INCOMING CONFIG FILES
      if [ "$VERBOSE" = "y" ]
      then
         echo "Preparing incoming configuration files for processing..."
         prepare_conf $SUBPKGNAME
      else
         prepare_conf $SUBPKGNAME 2>/dev/null 1>/dev/null
      fi

      ### FIXATE PACKAGE
      if [ "$VERBOSE" = "y" ]
      then
         echo "Fixating package [$SUBPKGNAME]..."
      else
         echo -n "Fixating package [$SUBPKGNAME]..."
      fi
      fixate_package $SUBPKGNAME $SUBPKG $SUBCTL

      if [ ! "$VERBOSE" = "y" ]
      then
         echo "done."
      fi

      echo
   fi
}

#
# restoredoctimes()
# Restore original timestamps for files in the /usr/doc directory.  This
# is part of the package design for all Slackware packages.  It's policy
# to maintain original timestamps for documentation.
#
# Parameters:   $1    The package name.
#               $2    The package tree.
#

restoredoctimes() {
   echo
   echo "Restoring original timestamps on /usr/doc files..."

   # get a list of files that fall into /usr/doc
   rm -rf $TMP/doc-$1
   mkdir -p $TMP/doc-$1
   find $2 -type f -print | grep "usr/doc" > $TMP/doc-$1/installed.lst

   # make a list of original files for each basename that got installed
   for installed in `cat $TMP/doc-$1/installed.lst`
   do
      find $TMP -type f -name `basename $installed` -print | \
         grep -v pkg-$1 >> \
         $TMP/doc-$1/`basename $installed`.orig.lst
   done

   # remove any lists that only consist of one file; messing with those
   # would just slow us down.
   for list in `find $TMP/doc-$1 -type f`
   do
      lines=`grep -c \$ $list`
      if [ $lines -eq 1 ]
      then
         rm -f $list
      fi
   done
 
   # loop of doom loop.
   #
   # for every file that got installed (in our installed.lst), do the
   # following:
   # 1) see if there is a corresponding basename.orig.lst
   # 1a) if not, that means there's only one file in $TMP with the same
   #     basename do a find in TMP and cp -a whatever turns up.
   # 1b) if so, loop through basename.orig.lst, comparing md5sums, until a
   #     match is found.  cp -a the matching file; all others are being
   #     written out to basename.orig.lst.checked, which is moved back to
   #     basename.orig.lst when done (thus eliminating matched file from
   #     list to be checked in the future)
   # 3) rinse, repeat.

   for installed in `cat $TMP/doc-$1/installed.lst`
   do
      base=`basename $installed`
      if [ ! -f "$TMP/doc-$1/$base.orig.lst" ]
      then
         original=$(find $TMP -type f -name `basename $installed` -print | \
                    grep -v pkg-$1)

         if [ "$VERBOSE" = "y" ]
         then
            # remove the TMP and PKG directory from the path
            tmpStart=`expr length $2`
            tmpStart=`expr $tmpStart + 1`
            tmpVar=$(expr substr $installed $tmpStart `expr length $installed`)

            # display the file we restored
            echo "   --> $tmpVar"

            # reclaim memory...maybe?...it's not exactly free() :)
            unset tmpVar tmpStart
         fi

         # We're now just touching files with the correct stamp instead of
         # recopying the file with cp -a
         #cp -a $original $installed
         touch -r $original $installed
      else
         installed_md5sum=`md5sum $installed | cut -d' ' -f1`
         nf=1
         for original in `cat $TMP/doc-$1/$base.orig.lst`
         do
            if [ "$nf" -a \
                 "$installed_md5sum" = "`md5sum $original | cut -d' ' -f1`" ]
            then
               unset nf
               # We're now just touching files with the correct stamp instead
               # of recopying the file with cp -a
               #cp -a $original $installed
               touch -r $original $installed
            else
               echo "$original" >> $TMP/doc-$1/$base.orig.lst.checked
            fi
         done

         mv $TMP/doc-$1/$base.orig.lst.checked \
            $TMP/doc-$1/$base.orig.lst
      fi
   done

   echo
}

#
# stripeverything()
# Goes through the package tree and strips all ELF stuff (binaries,
# libraries, and so on).
#
# Parameters:   $1    The package tree.
#

stripeverything() {
   cd $1

   STATIC_DONE=n

   # strip shared libraries and binaries
   if [ ! "$STRIPLIB" = "n" -o ! "$STRIPBIN" = "n" ]
   then
      msgOut=n

      for testname in `find . -type f`
      do
         islib="`file $testname | grep ELF | \
                                  grep 'shared object' | \
                                  grep 'not stripped'`"
         isprog="`file $testname | grep ELF | \
                                   grep executable | \
                                   grep 'not stripped'`"
         isstatic="`file $testname | grep 'current ar archive'`"

         # strip binaries if the user so desires
         if [ ! "$isprog" = "" -a "$STRIPBIN" = "y" ]
         then
            if [ "$msgOut" = "n" ]
            then
               echo
               echo "Stripping ELF objects, generating static library indexes..."
               msgOut=y
            fi

            echo "   --> strip -p $testname"
            strip -p $testname 2>/dev/null 1>/dev/null
         fi

         # strip libraries if the user so desires
         if [ ! "$islib" = "" -a "$STRIPLIB" = "y" ]
         then
            if [ "$msgOut" = "n" ]
            then
               echo 
               echo "Stripping ELF objects, generating static library indexes..."
               msgOut=y
            fi

            echo "   --> strip -p $testname"
            strip -p $testname 2>/dev/null 1>/dev/null
         fi

         # run ranlib on static libraries
         if [ ! "$isstatic" = "" ]
         then
            STATIC_DONE=y
            echo "   --> ranlib $testname"
            ranlib $testname 2>/dev/null 1>/dev/null
         fi
      done

      if [ "$msgOut" = "y" ]
      then
         echo
      fi
   fi

   if [ "$STATIC_DONE" = "n" ]
   then
      # ranlib static libraries
      msgOut=n
      for libname in `find . -name *.a -type f`
      do
         if [ ! "`file $libname | grep 'current ar archive'`" = "" ]
         then
            if [ "$msgOut" = "n" ]
            then
               echo 
               echo "Generating static library indexes..."
               msgOut=y
            fi

            echo "   --> $libname"
            ranlib $libname 2>/dev/null 1>/dev/null
         fi
      done
   fi
}

#
# symlinks()
# Called by protopkg() to remove any symlinks found in the temporary
# package build directory.  Also makes the doinst.sh file for recreating
# those at install time.
#
# Parameters:   $1    The package tree.
#               $2    The embedded package control directory.
#

symlinks() {
   # Get rid of possible pre-existing trouble:
   rm -rf $TMP/iNsT-a.$$

   cd $1
   if [ "$VERBOSE" = "y" ]
   then
      echo
      find . -type l -exec ls -l {} \; | cut -b58- | tee $TMP/iNsT-a.$$
   else
      find . -type l -exec ls -l {} \; | cut -b58- > $TMP/iNsT-a.$$
   fi

   cd $TMP

   # create the install directory if we need it
   if [ ! -d $2 ]
   then
      mkdir -p $2
      chown root.root $2
      chmod 755 $2
   fi

   if [ ! "`file_size $TMP/iNsT-a.$$`" = "0" ]
   then
      if [ "$VERBOSE" = "y" ]
      then
         echo
         make_install_script $TMP/iNsT-a.$$ | tee $TMP/doinst.sh
      else
         make_install_script $TMP/iNsT-a.$$ > $TMP/doinst.sh
      fi

      # write out the doinst script
      cat $TMP/doinst.sh >> $2/doinst.sh
      chown root.root $2/doinst.sh
      chmod 644 $2/doinst.sh

      # remove symlinks and clean up temp files
      cd $1
      if [ "$VERBOSE" = "y" ]
      then
         echo
         find . -type l -exec rm -v {} \;
      else
         find . -type l -exec rm {} \;
      fi
      cd $TMP ; rm -f doinst.sh iNsT-a.$$
   fi

   rm -rf $TMP/iNsT-a.$$
}

#
# trim_slash()
# Removes the first character of the passed in string if it's a slash.
#

trim_slash() {
   if [ "`echo $1 | cut -c1`" = "/" ]
   then
      expr substr $1 2 `expr length $1`
   fi
}

#
# verifyproto()
# Called by protopkg() after it finds a prototype file.  It checks to make
# sure you have a compile and install functions, as well as a PKGNAME
# variable.  Those are the minimum things needed to create a package.
#

verifyproto() {
   # checks to make sure the prototype file is good to go

   if [ ! "$1" = "sub" ]
   then
      SECT_INSTALL="`cat ./prototype | grep install\(\)`"
      SECT_COMPILE="`cat ./prototype | grep compile\(\)`"
      SECT_PKGNAME="`cat ./prototype | grep PKGNAME=`"
   else
      ### checking a subpackage prototype file
      SECT_INSTALL="`cat $CWD/prototype.$2 | grep subinstall\(\)`"
      SECT_COMPILE="hejaz"
      SECT_PKGNAME="`cat $CWD/prototype.$2 | grep SUBPKGNAME=`"
   fi

   if [ "$SECT_INSTALL" = "" ]
   then
      echo "BAD"
      if [ "$1" = "sub" ]
      then
         echo "You are missing the subinstall() section from your subprototype file."
      else
         echo "You are missing the install() section from your prototype file."
      fi
      exit
   fi

   if [ "$SECT_COMPILE" = "" ]
   then
      echo "BAD"
      echo "You are missing the compile() section from your prototype file."
      exit
   fi

   if [ "$SECT_PKGNAME" = "" ]
   then
      echo "BAD"
      if [ "$1" = "sub" ]
      then
         echo "You are missing the package name (SUBPKGNAME) from your subprototype file."
      else
         echo "You are missing the package name (PKGNAME) from your prototype file."
      fi
      exit
   fi
}

#
# zerocheck()
# Called by protopkg() to check for possible zero-length files that are in
# the package.  This usually indicates an error in the compile process.
#
# Parameters:   $1   The package tree.
#

zerocheck() {
   cd $1
   find . -type f -size 0c | while read file ; do
      echo "   WARNING: zero length file $file"
   done
   find . -type f -name '*.gz' -size 20c | while read file ; do
      echo "   WARNING: possible empty gzipped file $file"
   done
}

#
# protopkg_help()
#

protopkg_help() {
   cat << EOF
protopkg $VER
Create a new Slackware Linux software package.
Usage:  `basename $0` [options]
Options:
   -v, --verbose             Verbose mode.
   -c, --cleanup             Remove build tree after package is made.
   -b, --batch               Batch mode, do not present list of found files.
   -p<pkg>, --package <pkg>  Only build the specified subpackage.  If <pkg>
                             is null, only build the main package.
   -r, --remove              Remove the source archives after building.
   -d, --description         Generate a package description file for the
                             package(s) that are created.
   -h, --help                Display the help screen.
EOF
}

#
# protopkg()
# Main function for the protopkg operation.  Reads in a ./prototype file,
# verifies it, and uses it to make a valid package.
#

protopkg() {
   # look for a prototype file
   if [ ! -f ./prototype ]
   then
      echo "ERROR:  No package prototype file found."
      exit
   else
      . ./prototype
   fi

   # common variables
   TMP=$TMP/build-${PKGNAME}
   CWD=`pwd`
   COMPILED=n
   
   if [ ! -d $TMP ]
   then
      mkdir -p $TMP
      chmod 700 $TMP
   fi
   
   # set the ignore path
   IPATH="-path /proc -prune -o"
   
   if [ "$IGNOREPATH" != "" ]
   then
      IGNOREPATH="`echo $IGNOREPATH | sed -e s/\:/\ \-prune\ \-o\ \-path\ /g`"
      IPATH="$IPATH -path $IGNOREPATH -prune -o"
   fi
   
   echo "protopkg - version $VER"
   echo

   ### VERIFY PACKAGE PROTOTYPE FILE
   echo -n "Verifying package prototype file..."
   verifyproto
   echo "good, building package [$PKGNAME]"
   echo

   # are we only building one subpackage?
   if [ ! "$BUILD_SUBPACK" = "" ]
   then
      repack $BUILD_SUBPACK

      echo -n "Cleaning up temporary files..."
      if [ "$CLEANUP" = "y" ]
      then
         cd /
         rm -rf $TMP 2>/dev/null 1>/dev/null
         echo "done."
      else
         echo "skipping."
      fi

      exit
   fi

   ### FETCH SOURCE CODE IF WE NEED TO
   if [ -f $CWD/sources ]
   then
      echo -n "Fetching source code..."
      cd $CWD
      if [ "$VERBOSE" = "y" ]
      then
         echo
         download_sources $CWD/sources
      else
         download_sources $CWD/sources 2>/dev/null 1>/dev/null
         echo "done."
      fi
   fi
   
   ### COMPILE PROGRAM
   echo -n "Compiling package [$PKGNAME]..."
   cd $TMP
   if [ "$VERBOSE" = "y" ]
   then
      echo
      compile
   else
      compile 2>/dev/null 1>/dev/null
      echo "done."
   fi
   COMPILED=y
   
   ### BUILD LIST OF INSTALLED FILES
   echo
   echo -n "Generating list of files for package [$PKGNAME]..."
   beforeinstall
   if [ "$VERBOSE" = "y" ]
   then
      echo
      install
   else
      install 2>/dev/null 1>/dev/null
   fi
   afterinstall
   if [ ! "$VERBOSE" = "y" ]
   then
      echo "done."
   fi

   ### HAVE USER PLAY WITH PACKAGE LIST UNLESS BATCH MODE IS SET
   if [ ! "$BATCH" = "y" ]
   then
cat << EOF

protopkg has gathered a list of what it thinks should be in the package.
You will now be given the opportunity to edit that list if you see that
protopkg missed anything or got something wrong.  Just type in the editor
you want to use and hit enter to edit the package list.  After you exit
the editor, protopkg will finish making the package.

EOF
      echo -n "Editor to use [default: $EDITOR]: "
      read usered

      if [ "$usered" = "" ]
      then
         $EDITOR $TMP/package.lst
      else
         if [ ! -x $usered -a ! -f $usered ]
         then
            echo
            echo "Error with editor specified, defaulting to $EDITOR"
            $EDITOR $TMP/package.lst
         else
            $usered $TMP/package.lst
         fi
      fi
   fi
   echo

   ### Set the package and package control variables
   PKG=$TMP/pkg-$PKGNAME
   CTL=$PKG/install

   if [ "$VERBOSE" = "y" ]
   then
      echo "Creating package tree..."
      build_package $PKGNAME $PKG
      stripeverything $PKG
      restoredoctimes $PKGNAME $PKG
      permissionize $PKG
      compressdocs $PKG
   else
      echo -n "Creating package tree..."
      build_package $PKGNAME $PKG 2>/dev/null 1>/dev/null
      stripeverything $PKG 2>/dev/null 1>/dev/null
      restoredoctimes $PKGNAME $PKG 2>/dev/null 1>/dev/null
      permissionize $PKG 2>/dev/null 1>/dev/null
      compressdocs $PKG 2>/dev/null 1>/dev/null
   fi

   if [ ! "$VERBOSE" = "y" ]
   then
      echo "done."
   fi
   
   ### CHECK FOR ZERO-LENGTH FILES
   if [ "$VERBOSE" = "y" ]
   then
      echo "Checking for zero-length files..."
   fi
   zerocheck $PKG
   echo

   ### MAKE THE SYMLINKS FOR THE DOINST
   if [ "$VERBOSE" = "y" ]
   then
      symlinks $PKG $CTL
   else
      symlinks $PKG $CTL 2>/dev/null 1>/dev/null
   fi

   ### RUN THE SPECIAL FUNCTION
   cd $PKG
   if [ "$VERBOSE" = "y" ]
   then
      special
   else
      special 2>/dev/null 1>/dev/null
   fi

   ### READ THE RULES FILE AND PREPARE INCOMING CONFIG FILES
   if [ "$VERBOSE" = "y" ]
   then
      echo "Preparing incoming configuration files for processing..."
      prepare_conf
   else
      prepare_conf 2>/dev/null 1>/dev/null
   fi

   ### FIXATE PACKAGE
   if [ "$VERBOSE" = "y" ]
   then
      echo "Fixating package [$PKGNAME]..."
   else
      echo -n "Fixating package [$PKGNAME]..."
   fi

   fixate_package $PKGNAME $PKG $CTL

   if [ ! "$VERBOSE" = "y" ]
   then
      echo "done."
   fi

   ### HANDLE ANY SUBPACKAGES
   if [ ! "$SKIP_SUBPACKS" = "y" ]
   then
      if [ "$VERBOSE" = "y" ]
      then
         echo
         echo "Building subpackages..."
         subpacks
      else
         echo -n "Building subpackages..."
         subpacks 2>/dev/null 1>/dev/null
         echo "done."
      fi
   fi

   ### REMOVE SOURCE ARCHIVES IF WE NEED TO
   if [ "$REMOVE_SOURCES" = "y" ]
   then
      for f in `/bin/ls -1 $CWD/sources*`
      do
         cat $f | read_rules "sources" | (
            while read Surl Smd5
            do
               Sfile="`basename $Surl`"
               rm -rf $CWD/$Sfile 2>/dev/null 1>/dev/null
            done )
         unset Sfile Surl Smd5
      done
   fi

   ### CLEANUP
   echo
   echo -n "Cleaning up temporary files..."
   if [ "$CLEANUP" = "y" ]
   then
      cd /
      rm -rf $TMP 2>/dev/null 1>/dev/null
      echo "done."
   else
      echo "skipping."
   fi
}

###
### PROTOPKG
###

# parse the command line options
CL=`getopt -o vcbp::rdh --long verbose,cleanup,batch,package::,remove,description,help -- "$@"`

# no options, show the help screen and terminate
if [ ! $? = 0 ]
then
   protopkg_help
   exit
fi

# set the parsed options to be our new positional parameter list
eval set -- "$CL"

# evaluate the command line options
for OPT in $@
do
   case $OPT in
      "-v"|"--verbose")
         VERBOSE=y
         shift ;;
      "-c"|"--cleanup")
         CLEANUP=y
         shift ;;
      "-b"|"--batch")
         BATCH=y
         shift ;;
      "-p"|"--package")
         if [ "$2" = "" ]
         then
            SKIP_SUBPACKS=y
         else
            BUILD_SUBPACK=$2
         fi
         shift 2;;
      "-h"|"--help")
         protopkg_help
         exit ;;
      "-r"|"--remove")
         REMOVE_SOURCES=y
         shift ;;
      "-d"|"--description")
         CREATE_DESCRIPTION=y
         shift ;;
      --)
         shift
         break ;;
   esac
done

# install the packages the user specifies
protopkg ; exit
