##
##  rotate -- Logfile rotation
##  Copyright (c) 2001-2002 Ralf S. Engelschall <rse@engelschall.com>
##  Originally written for use in OpenPKG
##
##  This file is part of shtool and 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 file 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
##  USA, or contact Ralf S. Engelschall <rse@engelschall.com>.
##

str_tool="rotate"
str_usage="[-v|--verbose] [-t|--trace] [-f|--force] [-n|--num-files <count>] [-s|--size <size>] [-c|--copy] [-r|--remove] [-a|--archive-dir <dir>] [-z|--compress [<tool>:]<level>] [-b|--background] [-d|--delay] [-p|--pad <len>] [-o|--owner <owner>] [-g|--group <group>] [-m|--mode <mode>] [-M|--migrate <cmd>] [-P|--prolog <cmd>] [-E|--epilog <cmd>] <file> [...]"
arg_spec="1+"
opt_spec="v.t.f.n:s:c.r.a:z:b.d.p:o:g:m:M:P:E:"
opt_alias="v:verbose,t:trace,f:force,n:num-files,s:size,c:copy,r:remove,a:archive-dir,z:compress,b:background,d:delay,p:pad,o:owner,g:group,m:mode,M:migrate,P:prolog,E:epilog"
opt_v=no
opt_t=no
opt_f=no
opt_n=10
opt_s=""
opt_c=no
opt_r=no
opt_a=""
opt_z=""
opt_b=no
opt_d=no
opt_p=1
opt_o=""
opt_g=""
opt_m=""
opt_M=""
opt_P=""
opt_E=""

. ./sh.common

#   make sure we have at least one file to rotate
if [ ".$opt_n" = .0 ]; then
    echo "$msgprefix:Error: invalid argument \`$opt_n' to option -n." 1>&2
    exit 1
fi

#   canonicalize -s option argument
if [ ".$opt_s" != . ]; then
    if [ ".`expr $opt_s : '[0-9]*$'`" != .0 ]; then
        :
    elif [ ".`expr $opt_s : '[0-9]*[Kk]$'`" != .0 ]; then
        opt_s=`expr $opt_s : '\([0-9]*\)[Kk]$'`
        opt_s=`expr $opt_s \* 1024`
    elif [ ".`expr $opt_s : '[0-9]*[Mm]$'`" != .0 ]; then
        opt_s=`expr $opt_s : '\([0-9]*\)[Mm]$'`
        opt_s=`expr $opt_s \* 1048576` # 1024*1024
    elif [ ".`expr $opt_s : '[0-9]*[Gg]$'`" != .0 ]; then
        opt_s=`expr $opt_s : '\([0-9]*\)[Gg]$'`
        opt_s=`expr $opt_s \* 1073741824` # 1024*1024*1024
    else
        echo "$msgprefix:Error: invalid argument \`$opt_s' to option -s." 1>&2
        exit 1
    fi
fi

#   option -d/-z consistency
if [ ".$opt_d" = .yes -a ".$opt_z" = . ]; then
    echo "$msgprefix:Error: option -d requires option -z." 1>&2
    exit 1
fi

#   make sure target directory exists
if [ ".$opt_a" != . ]; then
    if [ ! -d $opt_a ]; then
        if [ ".$opt_f" = .no ]; then
            echo "$msgprefix:Error: archive directory \`$opt_a' does not exist." 1>&2
            exit 1
        fi
        mkdir $opt_a || exit $?
        chmod 755 $opt_a
    fi
    if [ ! -w $opt_a ]; then
        echo "$msgprefix:Error: archive directory \`$opt_a' not writable." 1>&2
        exit 1
    fi
fi

#   determine compression approach
if [ ".$opt_z" != . ]; then
    comp_lvl="$opt_z"
    comp_prg=""
    case $comp_lvl in
        *:* ) eval `echo $comp_lvl |\
                    sed -e 's%^\(.*\):\(.*\)$%comp_prg="\1"; comp_lvl="\2"%'` ;;
    esac

    #   compression level consistency
    case $comp_lvl in
        [0-9] )
            ;;
        * ) echo "$msgprefix:Error: invalid compression level \`$comp_lvl'" 1>&2
            exit 1
            ;;
    esac

    #   determine a suitable compression tool
    if [ ".$comp_prg" = . ]; then
        #   check whether the test command supports the -x option
        if [ -x /bin/sh ] 2>/dev/null; then
            minusx="-x"
        else
            minusx="-r"
        fi
        #   search for tools in $PATH
        paths="`echo $PATH |\
                sed -e 's%/*:%:%g' -e 's%/*$%%' \
                    -e 's/^:/.:/' -e 's/::/:.:/g' -e 's/:$/:./' \
                    -e 's/:/ /g'`"
        for prg in bzip2 gzip compress; do
            for path in $paths; do
                if [ $minusx "$path/$prg" -a ! -d "$path/$prg" ]; then
                    comp_prg="$prg"
                    break
                fi
            done
            if [ ".$comp_prg" != . ]; then
                break
            fi
        done
        if [ ".$comp_prg" = . ]; then
            echo "$msgprefix:Error: no suitable compression tool found in \$PATH" 1>&2
            exit 1
        fi
    fi

    #   determine standard compression extension
    #   and make sure it is a known tool
    case $comp_prg in
        */bzip2    | bzip2    ) comp_ext="bz2" comp_lvl="-$comp_lvl" ;;
        */gzip     | gzip     ) comp_ext="gz"  comp_lvl="-$comp_lvl" ;;
        */compress | compress ) comp_ext="Z";  comp_lvl=""           ;;
        * ) echo "$msgprefix:Error: tool \`$comp_prg' is not a known compression tool" 1>&2
            exit 1
            ;;
    esac
    comp_suf=".$comp_ext"
fi

#   iterate over all given logfile arguments
for file in $*; do
    #   make sure the logfile exists
    if [ ! -f $file ]; then
        if [ ".$opt_f" = .yes ]; then
            continue
        fi
        echo "$msgprefix:Error: logfile \`$file' not found" 1>&2
        exit 1
    fi

    #   determine log directory (where original logfile is placed)
    ldir="."
    case $file in
        */* ) eval `echo $file | sed -e 's%^\(.*\)/\([^/]*\)$%ldir="\1"; file="\2";%'` ;;
    esac

    #   determine archive directory (where rotated logfiles are placed)
    adir="$ldir"
    if [ ".$opt_a" != . ]; then
        case "$opt_a" in
            /* | ./* ) adir="$opt_a" ;;
            * ) adir="$ldir/$opt_a"  ;;
        esac
    fi

    #   optionally take logfile size into account
    if [ ".$opt_s" != . ]; then
        #   determine size of logfile
        set -- `ls -l $ldir/$file | sed -e 's; -> .*$;;' -e 's;[ 	][ 	]*; ;g'`
        n=`expr $# - 4`
        eval "size=\`echo \${$n}\`"

        #   skip logfile if size is still too small
        if [ $size -lt $opt_s ]; then
            if [ ".$opt_v" = .yes ]; then
                echo "$ldir/$file: still too small in size -- skipping"
            fi
            continue
        fi
    fi

    #   verbosity
    if [ ".$opt_v" = .yes ]; then
        echo "rotating $ldir/$file"
    fi

    #   execute prolog
    if [ ".$opt_P" != . ]; then
        if [ ".$opt_t" = .yes ]; then
            echo "$opt_P"
        fi
        eval $opt_P
        [ $? -ne 0 ] && exit $?
    fi

    #   kick away out-rotated logfile
    n=`expr $opt_n - 1`
    n=`echo dummy | awk "{ printf(\"%0${opt_p}d\", n); }" n=$n`
    if [ -f "${adir}/${file}.${n}${comp_suf}" ]; then
        #   optionally migrate away the out-rotated logfile
        if [ ".$opt_M" != . ]; then
            if [ ".$opt_t" = .yes ]; then
                echo "$opt_M ${adir}/${file}.${n}${comp_suf}"
            fi
            eval "$opt_M ${adir}/${file}.${n}${comp_suf}"
            [ $? -ne 0 ] && exit $?
        fi
        #   finally get rid of the out-rotated logfile
        if [ ".$opt_t" = .yes ]; then
            echo "rm -f ${adir}/${file}.${n}${comp_suf}"
        fi
        rm -f ${adir}/${file}.${n}${comp_suf} || exit $?
    fi

    #   rotate already archived logfiles
    while [ $n -gt 0 ]; do
        m=$n
        n=`expr $n - 1`
        n=`echo dummy | awk "{ printf(\"%0${opt_p}d\", n); }" n=$n`
        if [ $n -eq 0 -a ".$opt_d" = .yes ]; then
            #   special case: first rotation file under delayed compression situation
            if [ ! -f "${adir}/${file}.${n}" ]; then
                continue
            fi

            #   compress file (delayed)
            if [ ".$opt_b" = .yes ]; then
                if [ ".$opt_t" = .yes ]; then
                    echo "mv ${adir}/${file}.${n} ${adir}/${file}.${m}"
                fi
                mv ${adir}/${file}.${n} ${adir}/${file}.${m} || exit $?
                if [ ".$opt_t" = .yes ]; then
                    echo "(${comp_prg} ${comp_lvl} <${adir}/${file}.${m} >${adir}/${file}.${m}${comp_suf}; rm -f ${adir}/${file}.${m}) &"
                fi
                ( ${comp_prg} ${comp_lvl} \
                      <${adir}/${file}.${m} \
                      >${adir}/${file}.${m}${comp_suf} || exit $?
                  rm -f ${adir}/${file}.${m} || exit $?
                ) </dev/null >/dev/null 2>&1 &
            else
                if [ ".$opt_t" = .yes ]; then
                    echo "${comp_prg} ${comp_lvl} <${adir}/${file}.${n} >${adir}/${file}.${m}${comp_suf}"
                fi
                ${comp_prg} ${comp_lvl} \
                    <${adir}/${file}.${n} \
                    >${adir}/${file}.${m}${comp_suf} || exit $?
                if [ ".$opt_t" = .yes ]; then
                    echo "rm -f ${adir}/${file}.${n}"
                fi
                rm -f ${adir}/${file}.${n} || exit $?
            fi

            #   fix file attributes
            if [ ".$opt_o" != . ]; then
                if [ ".$opt_t" = .yes ]; then
                    echo "chown $opt_o ${adir}/${file}.${m}${comp_suf}"
                fi
                chown $opt_o ${adir}/${file}.${m}${comp_suf} || exit $?
            fi
            if [ ".$opt_g" != . ]; then
                if [ ".$opt_t" = .yes ]; then
                    echo "chgrp $opt_g ${adir}/${file}.${m}${comp_suf}"
                fi
                chgrp $opt_g ${adir}/${file}.${m}${comp_suf} || exit $?
            fi
            if [ ".$opt_m" != . ]; then
                if [ ".$opt_t" = .yes ]; then
                    echo "chmod $opt_m ${adir}/${file}.${m}${comp_suf}"
                fi
                chmod $opt_m ${adir}/${file}.${m}${comp_suf} || exit $?
            fi
        else
            #   standard case: second and following rotation file
            if [ ! -f "${adir}/${file}.${n}${comp_suf}" ]; then
                continue
            fi
            if [ ".$opt_t" = .yes ]; then
                echo "mv ${adir}/${file}.${n}${comp_suf} ${adir}/${file}.${m}${comp_suf}"
            fi
            mv ${adir}/${file}.${n}${comp_suf} ${adir}/${file}.${m}${comp_suf} || exit $?
        fi
    done

    #   move away current logfile
    if [ ".$opt_c" = .yes ]; then
        #   approach: copy[+truncate]
        if [ ".$opt_t" = .yes ]; then
            echo "cp -p ${ldir}/${file} ${adir}/${file}.${n}"
        fi
        cp -p ${ldir}/${file} ${adir}/${file}.${n} || exit $?
        if [ ".$opt_r" = .no ]; then
            if [ ".$opt_t" = .yes ]; then
                echo "cp /dev/null ${ldir}/${file}"
            fi
            cp /dev/null ${ldir}/${file} || exit $?
        fi
    else
        #   approach: move[+touch]
        if [ ".$opt_t" = .yes ]; then
            echo "mv ${ldir}/${file} ${adir}/${file}.${n}"
        fi
        mv ${ldir}/${file} ${adir}/${file}.${n} || exit $?
        if [ ".$opt_r" = .no ]; then
            if [ ".$opt_t" = .yes ]; then
                echo "touch ${ldir}/${file}"
            fi
            touch ${ldir}/${file} || exit $?
            #   fix file attributes
            if [ ".$opt_o" != . ]; then
                if [ ".$opt_t" = .yes ]; then
                    echo "chown $opt_o ${ldir}/${file}"
                fi
                chown $opt_o ${ldir}/${file} || exit $?
            fi
            if [ ".$opt_g" != . ]; then
                if [ ".$opt_t" = .yes ]; then
                    echo "chgrp $opt_g ${ldir}/${file}"
                fi
                chgrp $opt_g ${ldir}/${file} || exit $?
            fi
            if [ ".$opt_m" != . ]; then
                if [ ".$opt_t" = .yes ]; then
                    echo "chmod $opt_m ${ldir}/${file}"
                fi
                chmod $opt_m ${ldir}/${file} || exit $?
            fi
        fi
    fi

    #   regular compression step
    if [ ".$opt_z" != . -a ".$opt_d" = .no ]; then
        #   compress file
        if [ ".$opt_b" = .yes ]; then
            if [ ".$opt_t" = .yes ]; then
                echo "(${comp_prg} ${comp_lvl} <${adir}/${file}.${n} >${adir}/${file}.${n}${comp_suf}; rm -f ${adir}/${file}.${n}) &"
            fi
            ( ${comp_prg} ${comp_lvl} \
                  <${adir}/${file}.${n} \
                  >${adir}/${file}.${n}${comp_suf} || exit $?
              rm -f ${adir}/${file}.${n} || exit $?
            ) </dev/null >/dev/null 2>&1 &
        else
            if [ ".$opt_t" = .yes ]; then
                echo "${comp_prg} ${comp_lvl} <${adir}/${file}.${n} >${adir}/${file}.${n}${comp_suf}"
            fi
            ${comp_prg} ${comp_lvl} \
                <${adir}/${file}.${n} \
                >${adir}/${file}.${n}${comp_suf} || exit $?
            if [ ".$opt_t" = .yes ]; then
                echo "rm -f ${opt_a}${file}.${n}"
            fi
            rm -f ${adir}/${file}.${n} || exit $?
        fi

        #   fix file attributes
        if [ ".$opt_o" != . ]; then
            if [ ".$opt_t" = .yes ]; then
                echo "chown $opt_o ${adir}/${file}.${n}${comp_suf}"
            fi
            chown $opt_o ${adir}/${file}.${n}${comp_suf} || exit $?
        fi
        if [ ".$opt_g" != . ]; then
            if [ ".$opt_t" = .yes ]; then
                echo "chgrp $opt_g ${adir}/${file}.${n}${comp_suf}"
            fi
            chgrp $opt_g ${adir}/${file}.${n}${comp_suf} || exit $?
        fi
        if [ ".$opt_m" != . ]; then
            if [ ".$opt_t" = .yes ]; then
                echo "chmod $opt_m ${adir}/${file}.${n}${comp_suf}"
            fi
            chmod $opt_m ${adir}/${file}.${n}${comp_suf} || exit $?
        fi
    fi

    #   execute epilog
    if [ ".$opt_E" != . ]; then
        if [ ".$opt_t" = .yes ]; then
            echo "$opt_E"
        fi
        eval $opt_E
        [ $? -ne 0 ] && exit $?
    fi
done

