#!/bin/bash
# ====================[ run-crons                          ]====================
#                     [ Time-stamp: "2008-12-01 16:16:12 leycec" ]             
#
# If run as "root" (e.g., from the system crontab), this script examines
# "/etc/cron.[hourly|daily|weekly|monthly]/" for scripts to be executed and
# stores metadata about that last execution in "/var/spool/cron/lastrun".
#
# Otherwise, this script examines
# "~/cron.[hourly|daily|weekly|monthly]/" for scripts to be executed and
# stores metadata about that last execution in "~/var/spool/cron/lastrun".
#
# --------------------( CHANGELOG                          )--------------------
# 27 Apr 2008; Brian Curry <bcurry@freeshell.org> run-crons:
#     Replaced the path-specific (e.g., "/etc/") segments of this script with
#     path-agnostic configurability. Previously, primly due to those path-
#     specific segments, this script could only be called from the system
#     crontab (e.g., "/etc/crontab") to act on system-specific paths (e.g.,
#     "/etc/cron.daily/"); now, given the replacement of those paths with
#     path-agnostic configurability, this script may be called from both system
#     and user crontabs (e.g., "/etc/crontab" and "~/.crontab") to act on
#     both system- and user-specific paths (e.g., "/etc/cron.daily/" and
#     "~/etc/cron.daily/").
#
# 08 Mar 2005; Aaron Walker <ka0ttic@gentoo.org> run-crons:
#     Ignore the error messages from find caused by race conditions, since
#     we could care less about the error as long as the file has been removed.
#     See bug 8506.
#
# 06 May 2004; Aron Griffis <agriffis@gentoo.org> run-crons:
#     Make the locking actually work.  The old code was racy.
#     Thanks to Mathias Gumz in bug 45155 for some cleanups.
#
# 23 Jun 2002; Jon Nelson <jnelson@gentoo.org> run-crons:
#     fixed a race condition, where cron jobs and run-crons wanted to
#     delete touch files
#
# 20 Apr 2002; Thilo Bangert <bangert@gentoo.org> run-crons:
#     moved lastrun directory to /var/spool/cron/lastrun
#
# Author: Achim Gottinger <achim@gentoo.org>
#
# Mostly copied from SuSE

# ....................{ VARIABLES                          }....................
# Run cron job processes under this "niceness." Niceness is an integer
# describing the CPU scheduling priority to be allocated a process; it ranges
# from -20 (the most favorable priority) to 19 (the least favorable priority).
#
# By default, cron job processes are run with least favorable priority so as to
# minimize the CPU load on all other running processes.
NICENESS=19

# Run cron job processes under this "ioniceness" and ioniceness class.
# Ioniceness is a pair of integers describing the I/O scheduling priority to be
# allocated a process; the ioniceness class is either 1 (the most favorable
# priority, called "real time"), 2 (the normal priority, called "best effort),
# or 3 (the lowest priority, called "idle"), while the ioniceness itself is an
# integer ranging from 0 (the most favorable priority) to 7 (the least favorable
# priority).
#
# By default, cron job processes are run with least favorable priority so as to
# minimize the I/O load on all other running processes - but not the "idle"
# priority, as that could prevent a cron job process from running at or near its
# scheduled time.
IONICENESS_CLASS=2
IONICENESS=7

# ....................{ LOCK FILES                         }....................
WHOAMI=$(whoami) 

if [[ "$WHOAMI" == "root" ]]; then
  LOGDIR=/var/log/crond
  LOCKDIR=/var/spool/cron/lastrun
  CRONROOT=/etc/cron.
else
  LOGDIR=${HOME}/var/log/cron
  LOCKDIR=${HOME}/var/spool/cron/lastrun
  CRONROOT=${HOME}/etc/cron.
fi

mkdir -p ${LOGDIR}
mkdir -p ${LOCKDIR}
LOCKFILE=${LOCKDIR}/lock

# Make sure we're not running multiple instances at once.
# Try twice to lock, otherwise give up.
for ((i = 0; i < 2; i = i + 1)); do
  ln -sn $$ ${LOCKFILE} 2>/dev/null && break

  # lock failed, check for a running process.
  # handle both old- and new-style locking.
  cronpid=$(readlink ${LOCKFILE} 2>/dev/null) ||
  cronpid=$(cat ${LOCKFILE} 2>/dev/null) ||
  continue  # lockfile disappeared? try again

  # better than kill -0 because we can verify that it's really
  # another run-crons process
  if [[ $(</proc/${cronpid}/cmdline) == $(</proc/$$/cmdline) ]] 2>/dev/null; then
    # whoa, another process is really running
    exit 0
  else
    rm -f ${LOCKFILE}
  fi
done

# Check to make sure locking was successful
if [[ ! -L ${LOCKFILE} ]]; then
  echo "Can't create or read existing ${LOCKFILE}, giving up"
  exit 1
fi

# Set a trap to remove the lockfile when we're finished
trap "rm -f ${LOCKFILE}" 0 1 2 3 15

# ....................{ CRON JOBS                          }....................
# Determine whether this system provides "nice".
NICE=$(which nice 2>/dev/null)
if [[ $? = 0 -a -x "$NICE" ]]
then NICE="$NICE -n${NICENESS}"
else NICE=""
fi

# Determine whether this system provides "ionice".
IONICE=$(which ionice 2>/dev/null)
if [[ $? = 0 -a -x "$IONICE" ]]
then IONICE="$IONICE -c${IONICENESS_CLASS} -n${IONICENESS}"
else IONICE=""
fi

for BASE in hourly daily weekly monthly ; do
  CRONDIR=${CRONROOT}${BASE}

  test -d $CRONDIR || continue

  if [ -e ${LOCKDIR}/cron.$BASE ] ; then
    case $BASE in
    hourly)
      #>= 1 hour, 5 min -=> +65 min
      TIME="-cmin +65" ;;
    daily)
      #>= 1 day, 5 min -=> +1445 min
      TIME="-cmin +1445"  ;;
    weekly)
      #>= 1 week, 5 min -=> +10085 min
      TIME="-cmin +10085"  ;;
    monthly)
      #>= 31 days, 5 min -=> +44645 min
      TIME="-cmin +44645" ;;
    esac

    find ${LOCKDIR} -name cron.$BASE $TIME -exec rm {} \; &>/dev/null || true
  fi

  # if there is no touch file, make one then run the scripts
  if [ ! -e ${LOCKDIR}/cron.$BASE ] ; then
    touch ${LOCKDIR}/cron.$BASE

    set +e
    for SCRIPT in $CRONDIR/* ; do
      if [[ -x $SCRIPT && ! -d $SCRIPT ]]; then
        $NICE $IONICE $SCRIPT >> "$LOGDIR"/$(basename "$SCRIPT") 2>&1
      fi
    done
  fi
done

# Clean out bogus cron.$BASE files with future times
touch ${LOCKDIR}
find ${LOCKDIR} -newer ${LOCKDIR} -exec /bin/rm -f {} \; &>/dev/null || true

# --------------------( COPYRIGHT AND LICENSE              )--------------------
# The information below applies to everything in this distribution,
# except where noted.
# 
# This file 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 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 file; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
