#!/bin/bash
# No way I try to deal with a crippled sh just for POSIX foo.
-# Copyright (C) 2009-2015 Joerg Jaspert <joerg@debian.org>
+# Copyright (C) 2009-2016 Joerg Jaspert <joerg@debian.org>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# the important part here)
set -E
+# If the extglob shell option is enabled using the shopt builtin,
+# several extended pattern matching operators are recognized. We use
+# it for the POSSIBLEARGS and the first case ${ARGS} matching.
+shopt -s extglob
+
# And use one locale, no matter what the caller has set
export LANG=C.UTF-8
export LC_ALL=C.UTF-8
+# If run from crontab, CONFIGDIR will point to the correct dir
+# where we find the vars file
+configdir=${configdir:-"/srv/ftp-master.debian.org/dak/config/debian"}
+# import the general variable set. (This will overwrite configdir, but
+# it is expected to have the same value)
+export SCRIPTVARS=${configdir}/vars
+. ${SCRIPTVARS}
+
# One arg please
declare -lr ARG=${1:-"meh"}
-# set DEBUG if you want to see a little more logs (needs to be used more)
-DEBUG=${DEBUG:-0}
-
-# This doesn't catch calling us with an unknown argument, but it
-# catches missing args and saves a good bunch of processing time
-# (reading the scriptvars later is slow)
-if [[ ${ARG} == meh ]]; then
- cat - <<EOF
-This is the FTPMaster cronscript. It needs an argument or it won't do
-anything for you.
-
-Currently accepted Arguments:
-
- unchecked - Process the unchecked queue
- dinstall - Run a dinstall
- yearly, hourly, daily, weekly - Run that part
-
-EOF
- exit 0
-fi
-
-# import the general variable set.
-export SCRIPTVARS=/srv/ftp-master.debian.org/dak/config/debian/vars
-. $SCRIPTVARS
-
-# common functions are "outsourced"
-. "${configdir}/common"
-
# program name is the (lower cased) first argument.
PROGRAM="${ARG}"
-# Timestamp when we started
-NOW=$(date "+%Y.%m.%d-%H:%M:%S")
-
-# Which list of tasks should we run?
-declare -r TASKLIST="${configdir}/${PROGRAM}.tasks"
-
-# A logfile for every cron script
-LOGFILE="${logdir}/${PROGRAM}_${NOW}.log"
-
-# Each "cronscript" may have a variables and a functions file
-# that we source
-for what in variables functions; do
- if [[ -f ${configdir}/${PROGRAM}.${what} ]]; then
- . ${configdir}/${PROGRAM}.${what}
- fi
-done
-
-# Get rid of tempfiles at the end
-trap cleanup EXIT TERM HUP INT QUIT
+# set DEBUG if you want to see a little more logs (needs to be used more)
+DEBUG=${DEBUG:-0}
+# Check if the argument is a known one. If so, lock us so that only
+# one copy of the type of cronscript runs. The $type.tasks file is
+# mandantory, so use that for locking.
case ${ARG} in
- unchecked)
- # Do not run during dinstall
- if [[ -e ${LOCK_DAILY} ]]; then
- exit 0;
- fi
- # only run one cron.unchecked and also lock against hourly (newoverview)
- if ! lockfile -r8 ${LOCK_UNCHECKED} 2> /dev/null; then
- # log "aborting cron.unchecked because $LOCK_UNCHECKED has already been locked"
- exit 0
- fi
- TMPFILES="${TMPFILES} ${LOCK_UNCHECKED}"
- ;;
- dinstall)
- ;;
- hourly)
+ ${POSSIBLEARGS})
# Only one of me should ever run.
FLOCKER=${FLOCKER:-""}
- [ "${FLOCKER}" != "${configdir}/${PROGRAM}.variables" ] && exec env FLOCKER="${configdir}/${PROGRAM}.variables" flock -E 0 -en "${configdir}/${PROGRAM}.variables" "$0" "$@" || :
- ;;
- daily)
- ;;
- weekly)
- ;;
- monthly)
- ;;
- yearly)
+ [[ ${FLOCKER} != ${configdir}/${PROGRAM}.tasks ]] && exec env FLOCKER="${configdir}/${PROGRAM}.tasks" flock -E 0 -en "${configdir}/${PROGRAM}.tasks" "$0" "$@" || :
;;
*)
- error "Unknown arg ${ARG}"
- exit 42
- ;;
+ cat - <<EOF
+This is the cronscript. It needs an argument or it won't do anything
+for you.
+
+Currently accepted Arguments: ${POSSIBLEARGS}
+
+To see what they do, you want to look at the files
+\$ARGUMENT.{tasks,functions,variables} in ${configdir}.
+
+EOF
+ exit 0
+ ;;
esac
-# An easy access by name for the current log
-ln -sf ${LOGFILE} ${logdir}/${PROGRAM}
+(
+ LOCKFREE=0
+ flock --shared --nonblock 42 || LOCKFREE=1
-# And from here, all output to the log please
-exec >> "$LOGFILE" 2>&1
+ # Did we get the lock? (It's shared, so usually we will. But DSA
+ # can take an exclusive one in preparation for a reboot)
+ if [[ ${LOCKFREE} -gt 0 ]]; then
+ echo "Couldn't get (shared) reboot lock"
+ exit 1
+ fi
-# The stage function uses this directory
-# This amends the stagedir variable from "vars"
-stagedir="${stagedir}/${PROGRAM}"
-# Ensure the dir exists
-mkdir -p ${stagedir}
+ # common functions are "outsourced"
+ . "${configdir}/common"
-# This loop simply wants to be fed by a list of values (see below)
-# made out of 5 columns.
-# The first four are the array values for the stage function, the
-# fifth tells us if we should background the stage call.
-#
-# - FUNC - the function name to call
-# - ARGS - Possible arguments to hand to the function. Can be the empty string
-# - TIME - The timestamp name. Can be the empty string
-# - ERR - if this is the string false, then the call will be surrounded by
-# set +e ... set -e calls, so errors in the function do not exit
-# dinstall. Can be the empty string, meaning true.
-# - BG - Background the function stage?
-#
-# ATTENTION: Spaces in arguments or timestamp names need to be escaped by \
-#
-# NOTE 1: There are two special values for the first column (FUNC).
-# STATE - do not call stage function, call the state
-# function to update the public statefile "where is dinstall"
-# NOSTAGE - do not call stage function, call the command directly.
-#
-# Note 2: If you want to hand an empty value to the stage function,
-# use the word "none" in the list below.
-while read FUNC ARGS TIME ERR BACKGROUND; do
- debug "FUNC: $FUNC ARGS: $ARGS TIME: $TIME ERR: $ERR BG: $BACKGROUND"
-
- # Empty values in the value list are the string "none" (or the
- # while read loop won't work). Here we ensure that variables that
- # can be empty, are empty if the string none is set for them.
- for var in ARGS TIME; do
- if [[ ${!var} == none ]]; then
- typeset ${var}=''
- fi
- done
+ # Timestamp when we started
+ NOW=$(date "+%Y.%m.%d-%H:%M:%S")
+
+ # Which list of tasks should we run?
+ declare -r TASKLIST="${configdir}/${PROGRAM}.tasks"
+
+ # A logfile for every cron script
+ LOGFILE="${logdir}/${PROGRAM}_${NOW}.log"
- # ERR/BACKGROUND are boolean, check that they are.
- for var in ERR BACKGROUND; do
- if [[ ${!var} != false ]] && [[ ${!var} != true ]]; then
- error "Illegal value ${!var} for ${var} (should be true or false), line for function ${FUNC}"
+ # Each "cronscript" may have a variables and a functions file
+ # that we source
+ for what in variables functions; do
+ if [[ -f ${configdir}/${PROGRAM}.${what} ]]; then
+ . ${configdir}/${PROGRAM}.${what}
fi
done
- case ${FUNC} in
- STATE)
- state ${ARGS}
- ;;
- NOSTAGE)
- ${ARGS}
- ;;
- *)
- GO=(
- FUNC=${FUNC}
- TIME=${TIME}
- ARGS=${ARGS}
- ERR=${ERR}
- )
- if [[ ${BACKGROUND} == true ]]; then
- stage $GO &
- else
- stage $GO
- fi
- ;;
- esac
-done < <(grep -v '^#' ${TASKLIST} )
+ # Get rid of tempfiles at the end
+ trap cleanup EXIT TERM HUP INT QUIT
-# we need to wait for the background processes before the end of the cron script
-wait
+ # If there is a precronscript function, we run it.
+ prefunc=$(type -t precronscript || echo "")
+ if [[ -n ${prefunc} ]] && [[ ${prefunc} = function ]]; then
+ precronscript
+ fi
+ # An easy access by name for the current log
+ ln -sf ${LOGFILE} ${logdir}/${PROGRAM}
+
+ # And from here, all output to the log please
+ exec >> "$LOGFILE" 2>&1
+
+ # The stage function uses this directory
+ # This amends the stagedir variable from "vars"
+ stagedir="${stagedir}/${PROGRAM}"
+ # Ensure the dir exists
+ mkdir -p ${stagedir}
+
+ # This loop simply wants to be fed by a list of values (see below)
+ # made out of 5 columns.
+ # The first four are the array values for the stage function, the
+ # fifth tells us if we should background the stage call.
+ #
+ # - FUNC - the function name to call
+ # - ARGS - Possible arguments to hand to the function. Can be the empty string
+ # - TIME - The timestamp name. Can be the empty string
+ # - ERR - if this is the string false, then the call will be surrounded by
+ # set +e ... set -e calls, so errors in the function do not exit
+ # dinstall. Can be the empty string, meaning true.
+ # - BG - Background the function stage?
+ #
+ # ATTENTION: Spaces in arguments or timestamp names need to be escaped by \
+ #
+ # NOTE 1: There are two special values for the first column (FUNC).
+ # STATE - do not call stage function, call the state
+ # function to update the public statefile "where is dinstall"
+ # NOSTAGE - do not call stage function, call the command directly.
+ #
+ # Note 2: If you want to hand an empty value to the stage function,
+ # use the word "none" in the list below.
+ while read FUNC ARGS TIME ERR BACKGROUND; do
+ debug "FUNC: $FUNC ARGS: $ARGS TIME: $TIME ERR: $ERR BG: $BACKGROUND"
+
+ # Empty values in the value list are the string "none" (or the
+ # while read loop won't work). Here we ensure that variables that
+ # can be empty, are empty if the string none is set for them.
+ for var in ARGS TIME; do
+ if [[ ${!var} == none ]]; then
+ typeset ${var}=''
+ fi
+ done
-# Common to all cron scripts
-log "Cron script successful, all done"
-# Redirect output to another file, as we want to compress our logfile
-# and ensure its no longer used
-exec > "$logdir/after${PROGRAM}.log" 2>&1
+ # ERR/BACKGROUND are boolean, check that they are.
+ for var in ERR BACKGROUND; do
+ if [[ ${!var} != false ]] && [[ ${!var} != true ]]; then
+ error "Illegal value ${!var} for ${var} (should be true or false), line for function ${FUNC}"
+ fi
+ done
+
+ case ${FUNC} in
+ STATE)
+ state ${ARGS}
+ ;;
+ NOSTAGE)
+ ${ARGS}
+ ;;
+ *)
+ GO=(
+ FUNC=${FUNC}
+ TIME=${TIME}
+ ARGS=${ARGS}
+ ERR=${ERR}
+ )
+ if [[ ${BACKGROUND} == true ]]; then
+ stage $GO &
+ else
+ stage $GO
+ fi
+ ;;
+ esac
+ done < <(grep -v '^#' ${TASKLIST} )
+
+ # we need to wait for the background processes before the end of the cron script
+ wait
+
+ # Common to all cron scripts
+ log "Cron script successful, all done"
+ # Redirect output to another file, as we want to compress our logfile
+ # and ensure its no longer used
+ exec > "$logdir/after${PROGRAM}.log" 2>&1
+
+ # If there is a postcronscript function, we run it.
+ postfunc=$(type -t postcronscript || echo "")
+ if [[ -n ${postfunc} ]] && [[ ${postfunc} = function ]]; then
+ postcronscript
+ fi
-case ${ARG} in
- unchecked)
- ;;
- dinstall)
- logstats ${LOGFILE}
- state "all done"
- touch "${DINSTALLEND}"
- ;;
- hourly)
- ;;
- daily)
- ;;
- weekly)
- ;;
- monthly)
- ;;
- yearly)
- ;;
-esac
+ # Now, at the very (successful) end of this run, make sure we remove
+ # our stage files, so the next dinstall run will do it all again.
+ rm -f ${stagedir}/*
+ bzip2 -9 ${LOGFILE}
-# Now, at the very (successful) end of this run, make sure we remove
-# our stage files, so the next dinstall run will do it all again.
-rm -f ${stagedir}/*
-bzip2 -9 ${LOGFILE}
+ # Logfile should be gone, remove the symlink
+ [[ -L ${logdir}/${PROGRAM} ]] && [[ ! -f ${logdir}/${PROGRAM} ]] && rm -f ${logdir}/${PROGRAM} || log "Logfile still exists or symlink gone already? Something fishy going on"
-# Logfile should be gone, remove the symlink
-[[ -L ${logdir}/${PROGRAM} ]] && [[ ! -f ${logdir}/${PROGRAM} ]] && rm -f ${logdir}/${PROGRAM} || log "Logfile still exists or symlink gone already? Something fishy going on"
+ # FIXME: Mail the log when its non-empty
+ [[ -s "${logdir}/after${PROGRAM}.log" ]] || rm "${logdir}/after${PROGRAM}.log"
-# FIXME: Mail the log when its non-empty
-[[ -s "${logdir}/after${PROGRAM}.log" ]] || rm "${logdir}/after${PROGRAM}.log"
+# And end the reboot-locked part
+) 42</var/run/reboot-lock