#!/bin/sh # Copyright 1999-2021 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 # # this script looks into /etc/cron.[hourly|daily|weekly|monthly] # for scripts to be executed. The info about last run is stored in # /var/spool/cron/lastrun LOCKDIR="/var/lock" CRONSPOOLDIR="/var/spool/cron" LASTRUNDIR="${CRONSPOOLDIR}/lastrun" # This is the legacy lockfile that we need to clean up. GLOBAL_LOCKFILE="${LASTRUNDIR}/lock" # Usage: log # Log a message via syslog. log() { local level="$1" shift logger -i -p "cron.${level}" -t run-crons "$@" } # Usage: grab_lock # Grab the lock for to make sure we are the only instance. grab_lock() { local i cronpid cmdline1 cmdline2 local lockfile # Free whatever previous lock (if any) we held. free_lock # For the legacy global lock, don't try to create a full path. case $1 in /*) lockfile=$1 ;; *) lockfile="${LOCKDIR}/cron.$1" ;; esac # Try twice to lock, otherwise give up. i=0 while [ $(( i += 1 )) -le 2 ] ; do # Normally we should be able to grab the lock and get out of here fast. if ln -sn $$ "${lockfile}" 2>/dev/null ; then break fi # Locking failed, so check for a running process. # Handle both old- and new-style locking. # Delete the cat logic when GLOBAL_LOCKFILE is purged. # Note: Does not handle PID namespaces ... if ! cronpid=$(readlink "${lockfile}" 2>/dev/null) ; then if ! cronpid=$(cat "${lockfile}" 2>/dev/null) ; then # The lockfile disappeared? Try the whole thing again ... continue fi fi # This is better than kill -0 because we can verify that it's really # another run-crons process. # We have to send stderr to /dev/null for two reasons: # - If the process disappears, the cmdline file might not exist. # - The cmdline file contains NUL bytes, but bash-4.4+ warns when # you try to assign NUL bytes to variables. # It'd be nice to not do it for a lot of code, but there's not easy # alternative in shell code. We could `cat | tr`, but that'd waste # a bit more than just a simple cat. if ( cmdline1=$(cat "/proc/${cronpid}/cmdline") || : cmdline2=$(cat "/proc/$$/cmdline") [ "${cmdline1}" = "${cmdline2}" ] ) 2>/dev/null ; then # Whoa, another run-crons is really running. return 1 fi # The lockfile is pointing to a dead process so break it. # TODO: This is still racy if we're running more than one run-crons. rm -f "${lockfile}" 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 the lock file for free_lock to clean up. _LOCKFILE="${lockfile}" return 0 } # Prevent random env vars from messing with us. _LOCKFILE= # Set a trap to release the lockfile when we're finished. trap 'free_lock' EXIT HUP INT QUIT TERM # Usage: free_lock # Release the lock that we last grabbed. This does not nest! free_lock() { if [ -n "${_LOCKFILE}" ] ; then rm -f "${_LOCKFILE}" # Only break the lock once. _LOCKFILE= fi } EXIT_STATUS=0 # Grab the legacy global lock to smoothly handle upgrades. # We should drop this after like Dec 2016. if [ -L "${GLOBAL_LOCKFILE}" -o -f "${GLOBAL_LOCKFILE}" ] ; then if ! grab_lock "${GLOBAL_LOCKFILE}" ; then # An old process is still running -- abort. exit 0 fi # Now release the lock since we no longer care about it. free_lock fi for BASE in hourly daily weekly monthly ; do CRONDIR=/etc/cron.${BASE} test -d $CRONDIR || continue # Grab the lock for this specific dir. if ! grab_lock "${BASE}" ; then # Someone else is processing this dir, so skip it. continue fi # Blow away stale states for this particular dir. lastrunfile="${LASTRUNDIR}/cron.${BASE}" if [ -e "${lastrunfile}" ] ; 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 "${LASTRUNDIR}/" -name cron.$BASE $TIME -exec rm {} \; 2>/dev/null || : fi # if there is no state file, make one, then run the scripts. if [ ! -e "${lastrunfile}" ] ; then touch "${lastrunfile}" set +e for SCRIPT in $CRONDIR/* ; do if [ -x "${SCRIPT}" ] && [ ! -d "${SCRIPT}" ] ; then # Filter out files people do not expect to be executed. case ${SCRIPT} in .*|*~) continue ;; esac log info "($(whoami)) CMD (${SCRIPT})" $SCRIPT ret=$? if [ ${ret} -ne 0 ] ; then log err "CMD (${SCRIPT}) failed with exit status ${ret}" EXIT_STATUS=1 fi fi done fi done # Clean out bogus state files with future times. touch "${LASTRUNDIR}" find "${LASTRUNDIR}/" -newer "${LASTRUNDIR}" -exec /bin/rm -f {} \; 2>/dev/null || : exit ${EXIT_STATUS}