#!/bin/bash #=============================================================================== # packageModule.sh does the following: # o Packages each module in a module repository into a zip. # o A module zip is named according to its [NAME] from its conf. # o A module zip is only created if the module or its conf has changed # since its zip was created. # o The package catalog, mods.d.tar.gz, is created only if a conf has changed. # o Determines which module confs have no corresponding module. # o Determines which modules have no corresponding conf. #=============================================================================== # control what this program calls PATH=/bin:/usr/bin # Ensure that files that are created are rw by the owner and r by everyone else umask 0022 # Ensure that sed will not try to interpret files unset LANG # -A is a bash 4.0 new feature declare -A modules declare -A paths declare moddir="" declare zipdir="" declare owner="" declare do="" declare clean="echo" declare -i pkgcnt=0 declare -i catalog=0 declare cur_path declare last_path function usage { local msg=$1 local code=$2 [[ -n $msg ]] && echo $msg >&2 echo "Usage: $0 -m moddir [-z zipdir] [-l logfile] [-o owner:group ]" >&2 echo " $0 -h|-?" >&2 echo " -m moddir The source directory containing sub-directories mods.d and modules." >&2 echo " If the moddir is not properly set up, it is an error." >&2 echo " -z zipdir The destination directory for the zipped modules. [default: /tmp/zip]" >&2 echo " If the zipdir does not exist it is created." >&2 echo " -o owner The owner:group to create files as. Available to root only." >&2 echo " -l logfile The log to append all output." >&2 echo " -n Don't execute any command, but rather output to STDOUT what would have been done." >&2 echo " -k Clean up stale modules and confs. This option is independent of -n." >&2 echo " -c Unconditionally create the mods.d.tar.gz catalog." >&2 echo " -h|-? This usage statement" >&2 echo "Note: output is to STDERR unless otherwise noted." >&2 [[ -n $code ]] && exit $code } function package_module { local conf=$1 local modName="" local dataPath="" local -i installSize=-1 local zip_reason="" # This should always succeed! if [[ ! -f $conf ]] ; then echo "$Error: $conf does not exist" >&2 return 1 fi if [[ ! -r $conf ]] ; then echo "Error: $conf is not readable" >&2 return 1 fi [[ -n $owner && $(\stat --printf %U:%G $conf) != "$owner" ]] && $do \chown $owner $conf # Read the conf for the section [NAME], DataPath and InstallSize while read line ; do # Remove trailing \r from windows created files line=${line%$'\015'} if [[ $line =~ ^[[:space:]]*\[(.*)\][[:space:]]*$ ]] ; then # Grab what is between the [ and ] modName=${BASH_REMATCH[1]} elif [[ $line =~ ^DataPath= ]] ; then # Grab what is after the = # also remove the ./ that starts the value dataPath=${line#*=} elif [[ $line =~ ^InstallSize= ]] ; then # Grab what is after the = installSize=${line#*=} fi done < $conf # The dataPath of some modules has a prefix for filenames as the last element if [[ ! -d "$dataPath" ]] ; then # Since it is a prefix, remove the last element # so that it becomes a directory dataPath=${dataPath%/*} fi # If the path starts with ./ then remove it dataPath=${dataPath#./} # If the path ends in a / then remove it dataPath=${dataPath%/} # at this point $dataPath must be a valid dataPath if [[ ! -d $dataPath ]] ; then echo "Error in $conf: $dataPath is not a directory" >&2 # Move the bogus conf out of the way $clean \mv $conf $conf.bad catalog=1 return 1 fi # Remember modules that are valid modules[$modName]=1 # And their data path paths[$dataPath]=1 # Compute InstallSize currentSize=$(find $dataPath -type f -ls | awk '{ total += $7 }; END { print total }') if (( installSize == -1 )) ; then # Append to file echo "InstallSize not present in $conf. Appending." >&2 [[ -z $do ]] && echo "InstallSize=$currentSize" >> $conf zip_reason="Conf is newer than zip" catalog=1 elif (( installSize != currentSize )) ; then echo "InstallSize for $conf has changed from $installSize to $currentSize" >&2 $do \sed -i -e "s/^InstallSize=$installSize/InstallSize=$currentSize/" $conf zip_reason="Conf is newer than zip" catalog=1 fi # create the zip if one of the following is true # - it doesn't exist # - the conf is newer than the existing zip # - some part is newer than the existing zip if [[ ! -f $zipdir/$modName.zip ]] ; then zip_reason="Zip does not exist" elif [[ $zipdir/$modName.zip -ot $conf ]] ; then zip_reason="Conf is newer than zip" elif [[ -n $(find $dataPath -type f -newer $zipdir/$modName.zip) ]] ; then find $dataPath -type f -newer $zipdir/$modName.zip -ls ls -l $zipdir/$modName.zip zip_reason="Module is newer than zip" fi if [[ -n $zip_reason ]] ; then (( pkgcnt++ )) # the name of the zipped file FINAL_ZIP=$zipdir/$modName.zip # Zip to a temp file so that current zip stays valid TMP_ZIP=$zipdir/tmp-$modName.zip # Ensure that it does not exist [[ -e $TMP_ZIP ]] && $do \rm -f $TMP_ZIP echo "Create $zipdir/$modName.zip because: $zip_reason" >&2 $do \zip -q -r $TMP_ZIP $conf $dataPath # As long as the source and destination are on the same partition the move will be atomic # Note: Those that have the file prior to the move will still get it if [[ $? == 0 ]] ; then $do \mv $TMP_ZIP $FINAL_ZIP [[ -f $FINAL_ZIP && -n $owner && $(\stat --printf %U:%G $FINAL_ZIP) != "$owner" ]] && $do \chown $owner $FINAL_ZIP else echo "creating $TMP_ZIP failed" >&2 $do \rm -f $TMP_ZIP fi fi } function cache_confs { # Rebuild the module catalog, mods.d.tar.gz if one of the following is true # - it doesn't exist # - there is a conf that is newer than the existing catalog # - there is a conf that needs to be removed from the existing catalog if [[ ! -f mods.d.tar.gz ]] ; then catalog=1 elif [[ mods.d.tar.gz -ot $moddir/mods.d ]] ; then catalog=1 elif [[ -n $(find $moddir/mods.d -type f -name "*.conf" -newer mods.d.tar.gz) ]] ; then \find $moddir/mods.d -type f -name "*.conf" -newer mods.d.tar.gz catalog=1 fi # nothing to do ((catalog == 0 )) && return 0 # the name of the cache file FINAL_CACHE=mods.d.tar.gz # tar.gz to a temp file so that current cache stays valid TMP_CACHE=tmp-$FINAL_CACHE # Ensure that it does not exist [[ -e $TMP_CACHE ]] && $do \rm -f $TMP_CACHE # Only include the conf files $do \tar czf $TMP_CACHE mods.d/*.conf # As long as the source and destination are on the same partition the move will be atomic # Note: Those that have the file prior to the move will still get it if [[ $? == 0 ]] ; then $do \mv $TMP_CACHE $FINAL_CACHE [[ -f $FINAL_CACHE && -n $owner && $(\stat --printf %U:%G $FINAL_CACHE) != "$owner" ]] && $do \chown $owner $FINAL_CACHE else echo "creating $TMP_CACHE failed" >&2 $do \rm -f $TMP_CACHE fi } # A POSIX variable OPTIND=1 # Reset in case getopts has been used previously in the shell. OPTIND=1 # Reset in case getopts has been used previously in the shell. while getopts "h?m:z:o:l:nkc" opt ; do case "$opt" in m) moddir=$OPTARG ;; z) zipdir=$OPTARG ;; l) if [[ -f $OPTARG ]] ; then # Set up the log exec >> $OPTARG # Output stderr to the log exec 2>&1 fi ;; o) owner=$OPTARG ;; n) do=echo ;; k) clean="" ;; c) catalog=1 ;; h|\?) usage exit 0 ;; esac done echo "Running $0 $@" >&2 echo "on $(date)" >&2 shift $((OPTIND-1)) # Validate parameters (( $# == 0 )) || usage "Error: extra parameters: $@" 1 [[ -n $moddir ]] || usage "Invalid -m has an empty argument" 1 [[ -d $moddir ]] || usage "Error: $moddir does not exist" 1 [[ -d $moddir/mods.d ]] || usage "Error: $moddir/mods.d does not exist" 1 [[ -d $moddir/modules ]] || usage "Error: $moddir/modules does not exist" 1 [[ -z $do && -n $owner && $(id -u) != 0 ]] && usage "Error: Must be root to use -o owner:group" 1 # if -z parameter was not specified then set zipdir to the default [[ -n $zipdir ]] || zipdir=/tmp/zip [[ -d $zipdir ]] || $do \mkdir $zipdir # Expand moddir and zipdir to their full path moddir=$(readlink -f $moddir) zipdir=$(readlink -f $zipdir) # Work in the moddir cd $moddir for modconf in mods.d/*.conf ; do package_module $modconf done cache_confs # Look for data paths not found in any conf for path in $(find modules -type f -exec dirname {} \; | sort -u) ; do if [[ -z ${paths[$path]} && -z ${paths[$(dirname $path)]} ]] ; then echo "Module without conf found at: $path" >&2 $clean \rm -rf $path fi done # Now work in the zip dir to remove zips that no longer have confs cd $zipdir for zip in *.zip ; do mod=${zip%.zip} if [[ -e $zip && -z ${modules[$mod]} ]] ; then echo "module $mod is no longer in the repository" >&2 $clean \rm -f $zip fi done exit 0