#!/bin/bash
# ------------------------------------------------------------------
#
# LFS package management system  
#
# Copyright 2005,  USM Bish <bish@touchtelindia.net>.
#
# 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.
#
# Usage:
#
# lfs-pkg [ -h | --help ] [-I] [-U string] [-v] [-l string] [-f string]
#
# Options:
#
# -h, --help		Display this help message and exit.
# -v              	View all packages installed
# -l  string		List contents of a specified package
# -f  string		Find which package a file belongs to
# -I                    Install (make install) [root privilege]
# -U  string            Un-Install [root privilege]
#
# Revision History:
#
# 15/10/2005    First prototype		        	ver 0.1
# 23/10/2005	First alpha release	        	ver 0.2
# 04/11/2005    Integrated single script         	ver 0.3
# 08/11/2005    Check for if already installed		ver 0.4
#
# -------------------------------------------------------------------

# -------------------------------------------------------------------
# Constants
# -------------------------------------------------------------------

  PROGNAME=$(basename $0)
  VERSION="0.3"

# -------------------------------------------------------------------
# User editable variables
# -------------------------------------------------------------------

# What is the pager to use ?
  MY_PAGER="less"
  
# Dir to save that installed packages info
  PKG_DIR="/var/lfs/packages"

# Directory where deleted package archives are kept
  ATTIC_DIR="/var/lfs/attic" 

# Where is installwatch installed ?
  INSTALLWATCH="/usr/local/bin/installwatch"
  
# -------------------------------------------------------------------
# Functions
# -------------------------------------------------------------------

function install
{
    check_root
    chk_installwatch
    
    ########################################################
    # Part I - Peliminaries
    ########################################################

    # Since most source packages are in the form package-version
    # we take the filename from the build directory itself

    PROG=$(basename `pwd`)
    DATE=$(date '+%d-%B-%Y [%a] [%R %Z]')                                                                        
    
    # Get the build architecture from uname. 
    ARCH=$(uname -m)
    PROC=$(uname -p)
    PLAT=$(uname -i)

    # Package naming would be : name-version-arch-processor
    # Change this if you want another convention
    PROG_ARCH="$PROG-$ARCH-$PROC"

    echo "###################################################"
    echo
    echo "Commencing make install with installwatch activated"
    echo "Architecture      = $ARCH"
    echo "Processor         = $PROC"
    echo "Hardware Platform = $PLAT" 
    echo "Package           = $PROG"
    echo
    echo "###################################################"
    echo

    echo "o Breakpoint #1 of 4 : Package name verification:"
    echo
    echo "Default Package = $PROG_ARCH"
    echo "Hit [Enter] if the default package name is okay, or"
    echo -en "Edit package name : "
    read O
    if ! [ -z "$O" ]; then
       PROG_ARCH=$O
       echo "New package name = $PROG_ARCH"
       echo -en "Hit [Enter] to continue ..."
       read
    fi

    #######################################################
    # Check if already installed before (ver 0.4)
    #######################################################
    
    if [ -e $PKG_DIR/$PROG_ARCH ]; then
       echo
       echo "An older installation of this name exists"
       echo "Package Name : $PROG_ARCH"
       echo "It is advisable to remove the old package "
       echo "Instead of overwriting it, since some old "
       echo "files would stay behind. Run [lfs-pkg -I] "
       echo "Advised to answer [N] to next question .. "
       echo
       echo -en "Overwrite ? [Y/N] : "
       read YN

       if [ $YN = "y" ] || [ $YN = "Y" ]; then
          echo
          echo "Will overwrite ... risks are yours !"
          echo "Hit [Enter] to proceed"
          read
       else
          echo
          echo "Good ... This makes sense !"
          graceful_exit
       fi   
    fi
               
    #######################################################
    # Part 2 : Check command line parameters
    #######################################################

    echo
    echo "Installing package : $PROG_ARCH"
    echo "o Breakpoint #2 of 4 : Command line input"
    echo
    CMD=""
    while [ -z "$CMD" ]; do
       echo -en "Command to execute : "
       read CMD
    done
    echo   
    echo "Will execute command : [$CMD]"
    echo
    echo "Hit [Ctrl-C] to abort if above is incorrect, else"
    echo "Press [Enter] to proceed ..."
    read

    ########################################################
    # Part 3 : The main thing where make is performed under
    # installwatch logging. Raw output : /tmp/inst.log
    ########################################################

    INST=/tmp/inst.log
    > $INST
    $INSTALLWATCH  --backup=no --logfile=$INST $CMD 

    E=$?
    if [ $E -ne 0 ]; then
       echo
       echo "###################################################"
       echo "Installation failed"
       echo "###################################################"
       echo
       signal_exit TERM
    else
       echo
       echo "###################################################"
       echo "Installation seems okay. Following installed : "
       echo "###################################################"
       echo    
    fi

    ########################################################
    # Part 4 : The filtering of raw output
    ########################################################

    ########################################################
    # This is the main filtering section. The code is JUNK ! 
    # but kept deliberately so ( to check where  things have
    # gone wrong !) Six files to check errors ? Horrible !!!
    # BUT, this is what ALPHA is about ! This will change.
    ######################################################## 

    INFILE=/tmp/inst.log

    # Remove references of /dev/tty
    cat /tmp/inst.log | grep -v '/dev/tty'  > /tmp/junk.1
    # Remove references of /tmp
    cat /tmp/junk.1 | grep -v '/tmp' > /tmp/junk.2
    # Remove references of pwd (if any)
    PD=$(pwd)
    cat /tmp/junk.2 | grep -v $PD > /tmp/junk.3
    # Remove all chmod references
    cat /tmp/junk.3 | grep -v 'chmod' > /tmp/junk.4
    # Remove all unlink references
    cat /tmp/junk.4 | grep -v 'unlink' > /tmp/junk.5
    # Remove all mkdir references
    cat /tmp/junk.5 | grep -v 'mkdir' > /tmp/junk.6
    # Skip files in /etc for logging purposes
    cat /tmp/junk.6 | grep -v '/etc' > /tmp/junk.7

    ###################################################
    # Now we are left with three labels, open, rename,
    # and link in the second field
    # From now on, we process line by line. 
    ###################################################

    # The temporary processes
    JUNK="/tmp/junk.7"
    OUT="/tmp/install.log"
    > $OUT

    MAX=$(wc $JUNK | awk '{print $1}')
    CNT=0
    FYLECNT=0

    while [ $CNT -le $MAX ]; do
        CNT=$((CNT+1))
        # Pick out each line
        LYNE=$(head -$CNT $JUNK | tail -1)
        # Get the second parameter
        SECOND=$(echo $LYNE | awk {'print $2}')

        if [ $SECOND = "open" ]; then
           TARGET=$(echo $LYNE | awk {'print $3}')
        else
           TARGET=$(echo $LYNE | awk {'print $4}')   
        fi
    
        # If file exists (excluding /dev/null) then write it
        if [ -e $TARGET ] && ! [ $TARGET = "/dev/null" ]; then
           FYLECNT=$((FYLECNT+1))
           echo "$FYLECNT] : $TARGET"
           echo $TARGET >> $OUT
        fi
    done

    # Line added to eliminate duplicate entries

    cat $OUT | sort | uniq > $OUT.tmp
    mv $OUT.tmp $OUT

    ################################################
    # Part 5 : Write the actual install file
    ################################################

    TEMP_FILE="/tmp/$PROG_ARCH"
    check_pkg_dir

    # If installation if okay so far then continue   
    echo
    echo "# Package Name      = $PROG_ARCH"       > $TEMP_FILE
    echo "# Installation Date = $DATE"           >> $TEMP_FILE
    cat $OUT >> $TEMP_FILE

    echo
    echo "###################################################"
    echo "Installation complete"
    echo "###################################################"
    echo
    echo "Break point #3 of 4 : View Installation"
    echo -en "Want to view package details in pager ? [y/n] ... "
    read YN

    if [ $YN = "" ]; then
       YN="y"
    fi

    if [ $YN = "y" ] || [ $YN = "Y" ]; then
       echo
       echo
       cat $TEMP_FILE | $MY_PAGER
       echo
       echo
    fi   

    #######################################################
    # Part 6 : Clean up
    #######################################################

    #######################################################
    # Once satisfied, just uncomment the lines below and
    # delete the section after the commented 'exit'
    #######################################################
    # OUTFILE="$PKG_DIR/$PROG_ARCH"
    # cat $TEMP_FILE > $OUTFILE
    # rm -f /tmp/inst.log /tmp/install.log /tmp/junk.*
    # rm -f $TEMP_FILE
    # echo "Done ..."
    # exit

    ######################################################
    # DELETE BELOW/ UNCOMMENT ABOVE ONCE THINGS ARE OKAY
    ###################################################### 

    echo
    echo "###################################################"
    echo "Last bit: Confirm commit"
    echo "###################################################"
    echo
    OUTFILE="$PKG_DIR/$PROG_ARCH"
    echo "Break point #4 of 4"
    echo -en "Write : $OUTFILE ? [y/n] "
    read YN
    if [ "$YN" = "y" ] || [ "$YN" = "Y" ]; then
       cat $TEMP_FILE > $OUTFILE
       #rm -f $TEMP_FILE
    fi

    echo "Done ... "
    graceful_exit
}
    
function uninstall
{
    check_root
    
    WILDCARD=$SRCH    
    CNT=0
    TOT=0
    
    echo "Searching for : $WILDCARD"
    for i in `ls $PKG_DIR`; do
       CNT=$((CNT+1))
       echo $i | grep -v "$WILDCARD" > /tmp/$PROGNAME.1
       if ! [ -s /tmp/$PROGNAME.1 ]; then
           TOT=$((TOT+1))
           echo "$TOT] Found : $i [matching search-word ($WILDCARD)]"
           TODEL[$TOT]=$i
       fi
    done
   
    if [ "$TOT" -eq 0 ]; then
       echo "NIL packages matching $WILDCARD"
       graceful_exit
    fi
      
    echo
    echo "That is all matching search-word : [$WILDCARD]"
    echo "Please answer YES [all uppercase] to delete "
    echo
   
    T=0        
    while [ $T -lt $TOT ]; do
        T=$((T+1))
        echo -en "$T] Delete ${TODEL[$T]} [YES/NO] : "
        read YN
        if [ "$YN" = "YES" ]; then
           echo
           echo "Creating backup package : ${TODEL[$T]}.tar.bz2"
           cat $PKG_DIR/${TODEL[$T]} | grep -v \# > /tmp/package.rmv 
           tar -cjf /tmp/backup.tar.bz2 -T /tmp/package.rmv 
           echo -en "Do you want to view on your pager what would be deleted ? [y/n] : "
           read YN1
           if [ $YN1 = "Y" ] || [ $YN1 = "y" ]; then
              tar -tjvf /tmp/backup.tar.bz2 | awk '{print $6}' | less
           fi   
           echo -en "Do you want to preserve this binary backup ? [y/n] : "
           read YN2
           if [ $YN2 = "Y" ] || [ $YN2 = "y" ]; then
              BAK=$ATTIC_DIR/${TODEL[$T]}-bin.tar.bz2
              mv /tmp/backup.tar.bz2 $BAK
              echo "Backup retained at : $BAK"
           fi

           MAX=$(wc /tmp/package.rmv | awk '{print $1}')
           echo "A total of $((MAX-1)) files would be removed for this package"
           echo "Press [Enter] to begin deletion process"
           read

           # main deletion routine
           CNT=0
           while [ $CNT -lt $MAX ]; do
              CNT=$((CNT+1))
              FYLENAME=$(head -$CNT /tmp/package.rmv | tail -1)
              if [ -f $FYLENAME ]; then
                 echo -en "$CNT] \t Deleting : $FYLENAME \t"
                 rm -f $FYLENAME
                 ERR=$?
                 echo -en "\\033[70G"
                 if [ $ERR = 0 ]; then
                    echo -en "[Ok]\n"
                 else
                    echo -en "[ERR]\n"
                 fi
              fi   
           done
           
           # Lastly remove the package from PKG_DIR
           rm -f $PKG_DIR/${TODEL[$T]} 
                     
           echo
           echo "$T] ${TODEL[$T]} ... Successfully Deleted"
           echo "Hit [Enter] for the next"              
           echo
        else
           echo "Left alone, [YES] not typed ;-)"   
        fi   
   done
   echo
   echo "Package uninstallation complete"
   rm -f /tmp/$PROGNAME.1
   graceful_exit
}

function clean_up
{
    rm -f ${TEMP_FILE1}
    rm -f ${TEMP_FILE2}
}

function error_exit
{
   echo "${PROGNAME}: ${1:-"Unknown Error"}" >&2
   clean_up
   exit 1
}

function graceful_exit
{
   clean_up
   exit
}

function signal_exit
{
   case $1 in
      INT) echo "$PROGNAME: Program aborted by user" >&2
           clean_up
           exit
           ;;
      TERM) echo "$PROGNAME: Program terminated" >&2
	   clean_up
           exit
           ;;
      *)   error_exit "$PROGNAME: Terminating on unknown signal"
           ;;
   esac
}

function make_temp_files
{
    # Use user's local tmp directory if it exists
    if [ -d ~/tmp ]; then
       TEMP_DIR=~/tmp
    else
       TEMP_DIR=/tmp
    fi

    # Temp file for this script, using paranoid method of creation 
    # to insure that file name is not predictable.  
    # This is for security to  avoid "tmp race"  attacks. 

    MKTEMP=`which mktemp`
    if [ -z $MKTEMP ]; then
       TEMP_FILE1=${TEMP_DIR}/${PROGNAME}.1.$$
       TEMP_FILE2=${TEMP_DIR}/${PROGNAME}.2.$$
    else   
       TEMP_FILE1=$($MKTEMP -q "${TEMP_DIR}/${PROGNAME}.1.$$.XXXXXX")
       TEMP_FILE2=$($MKTEMP -q "${TEMP_DIR}/${PROGNAME}.2.$$.XXXXXX")
    fi
    
    # Quit if temp file cannot be created   
    if [ "$TEMP_FILE1" = "" ] || [ "$TEMP_FILE2" = "" ]; then
        error_exit "cannot create temp file!"
    fi
}

function usage
{
   echo "Usage: ${PROGNAME} [-h | --help] [-I] [-U string] [-v] [-l string] [-f string]"
}

function helptext
{
   local tab=$(echo -en "\t\t")

   cat <<- -EOF-

   ${PROGNAME}                                            [ver. ${VERSION}]

   This script is meant for LFS package management 

   $(usage)

   Options:

   -h, --help                 Display this help message and exit.
   -v                         View names of all packages installed
   -l  search_string          List contents of a specified package
   -f  search_string          Find which package a file belongs to

   -I                         Install     [root privileges]
   -U  search_string          Un-Install  [root privileges]
   
   Note: search_string may be part name of package as well, in which 
         case all packages matching the search_string are displayed 	

   	
-EOF-
}

function check_root
{
    if [ "$UID" -ne 0 ]; then
       echo "$PROGNAME - [ver. $VERSION]"
       echo "ERR : For root access only"
       signal_exit TERM
    fi
}

function check_pkg_dir
{
   # Edit if you change location 

   if ! [ -d $PKG_DIR ]; then
      mkdir -p $PKG_DIR]
   fi
}

function chk_installwatch
{
   if ! [ -x "$INSTALLWATCH" ]; then
      echo "ERR : $INSTALLWATCH binary not found"
      signal_exit TERM
   fi         
}

# This is involed with '-v' in command line
function view_all_packages
{
    make_temp_files
    ls $PKG_DIR    > $TEMP_FILE1
    echo -en "These are the packages installed on your system as on:\n" > $TEMP_FILE2
    date >> $TEMP_FILE2
    echo -en "\nThis is on your pager : [$MY_PAGER]\n\n" >> $TEMP_FILE2
    cat -n $TEMP_FILE1 >> $TEMP_FILE2
    cat $TEMP_FILE2 | $MY_PAGER
    clean_up
    graceful_exit
}    

# This is invoked with '-l search-string' in command line
function list_contents
{
   CNT=0
   TOT=0
   echo "Searching for : $SRCH"

   for i in `ls $PKG_DIR`; do
      CNT=$((CNT+1))
      GOTIT=$(echo $i | grep "$SRCH")
      if ! [ -z $GOTIT ]; then
         TOT=$((TOT+1))
         FOUND[$TOT]=$GOTIT
      fi
   done
   echo "Total packages installed = $CNT"
   echo "Search matches for $SRCH = $TOT"
   
   if [ $TOT -gt 0 ]; then
      T=0
      while [ $T -lt $TOT ]; do
         T=$((T+1))
         echo "$T ] ${FOUND[$T]}"
      done
      U=0
      while [ $U -lt $TOT ]; do
         U=$((U+1))
         PKG=${FOUND[$U]}
         echo
         echo "Viewing package No : $U"
         echo "Press [Enter] to view contents of $PKG in $MY_PAGER"
         read
         echo
         cat $PKG_DIR/$PKG | $MY_PAGER
      done
      echo
      echo "Done .."
   fi
}

# This is invoked with '-f search-string' in command line
function find_contents
{
   make_temp_files
   echo "Searching for : $SRCH"
   PD=`pwd`
   cd $PKG_DIR   
   grep -li $SRCH * > $TEMP_FILE1
   cd $PD
   
   if [ -s $TEMP_FILE1 ]; then
      echo
      echo "Found the string [$SRCH] occuring in following packages: "
      echo "Please note, the search is case insensitive"
      echo
      cat -n $TEMP_FILE1
      echo
      echo "Hit [Enter] to view exact matches in $MY_PAGER pager"
      read
      echo -en "Search for word [$SRCH] in all packages \n\n" > $TEMP_FILE2
      T=$(wc $TEMP_FILE1 | awk '{print $1}')
      CNT=0
      while [ $CNT -lt $T ]; do
         CNT=$((CNT+1))
         PKG=$(head -$CNT $TEMP_FILE1 | tail -1)
         echo -en "Package: #$CNT] $PKG \n\n" >> $TEMP_FILE2
         cd $PKG_DIR
         grep -i $SRCH $PKG_DIR/$PKG              >> $TEMP_FILE2
         echo -en "\n--------------------------\n\n"  >> $TEMP_FILE2
      done
      $MY_PAGER $TEMP_FILE2   
   else
      echo "Are you sure that you are searching for : [$SRCH] ?"
      echo "This does not seem to belong to any installed package !"
      echo
   fi
   clean_up
}


# -------------------------------------------------------------------
# Program starts here
# -------------------------------------------------------------------

##### Initialization And Setup #####

# Set file creation mask so that all files are created with 600 permissions.

umask 066

# Trap TERM, HUP, and INT signals and properly exit

trap "signal_exit TERM" TERM HUP
trap "signal_exit INT"  INT

##### Command Line Processing #####

if [ "$1" = "--help" ]; then
    helptext
    graceful_exit
fi

if [ "$1" = "" ]; then
    usage
    graceful_exit
fi

echo "$PROGNAME Ver-[$VERSION] : LFS package management"

while getopts ":hvl:f:U:I" opt; do

   case $opt in

   v )	view_all_packages
        ;;

   l )  SRCH=$OPTARG
        list_contents         
        ;;
	
   f )	SRCH=$OPTARG
        find_contents
        ;;
                
   h )	helptext
	graceful_exit 
	;;
	
   U )  if ! [ -d "$ATTIC_DIR" ]; then
           mkdir -p $ATTIC_DIR
        fi
        SRCH=$OPTARG
        uninstall
        ;;
        
   I )  if ! [ -d "$PKG_DIR" ]; then
           mkdir -p $PKG_DIR
        fi
        install
        ;;

   * )	usage
	clean_up
	exit 1

   esac
done

graceful_exit

####################################################################
# That is all there is to it !
####################################################################
