#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8

function display_help() {
    uecho "$scriptname: lancer une suite de commande en respectant une planification de type cron

USAGE
    $scriptname [options] /path/to/crontab

Ce script doit normalement être lancé toutes les minutes par une tâche cron. A
chaque lancement, il examine quels scripts doivent être exécutés dans le fichier
crontab spécifié. Ce fichier est composé de lignes d'une des formes suivantes,
qui sont analysées et traitées dans l'ordre:

    # commentaire ignoré
    var=\"initialiser une variable\"
    minutes hours days months dows command [args]

Quelles que soient les lignes qui sont sélectionnées pour le lancement, elles
sont garanties de s'exécuter sériellement dans l'ordre du fichier, d'où le nom
de ce script.

Certaines extensions par rapport à la syntaxe de crontab sont autorisées. Il est
en particulier possible de spécifier plusieurs planifications pour une seule
commande. Par exemple, la ligne suivante permet d'exécuter 'command' toutes les
heures ET à 1h05:

    0 * * * *
    5 1 * * * command

De plus, il est possible de manipuler les variables de type PATH avec une
syntaxe particulière de l'opérateur d'assignation. Les opérateurs += et %=
utilisent uaddpath(), #= utilise uinspath() et -= utilise udelpath().  Par
exemple, les lignes suivantes ajoutent respectivement /usr/local/nutools puis
enlèvent /opt/rogue au PATH:

    PATH+=/usr/local/nutools
    PATH-=/opt/rogue


OPTIONS
    --install
        Installer une planification toutes les minutes de ce script dans la
        crontab de l'utilisateur.
    --uninstall
        Désinstaller la planification toutes les minutes de ce script du crontab
        de l'utilisateur.
    --lock LOCKFILE
        Inscrire dans le fichier spécifié des informations permettant d'éviter
        les invocations simultanées de ce script. Si selon ce fichier, le script
        tourne depuis plus de $LOCKDELAY heures, un message d'erreur est loggé
        et un message d'avertissement est affiché au plus une fois.
        Utiliser --lock '' pour désactiver cette fonctionnalité
        Par défaut, si ce script est lancé en root, le fichier utilisé pour le
        verrouillage est de la forme /var/run/$scriptname/abspath/to/crontab
        Si le script est lancé avec un compte utilisateur, aucun verrouillage
        n'est effectué.
    --lockdelay LOCKDELAY[=$LOCKDELAY]
        Changer le nombre d'heures pendant lesquelles on autorise le script a
        verrouiller l'exécution avant d'afficher un avertissement.
    -n, --fake
        Afficher au lieu de les exécuter les commandes qui doivent être lancées
    -c, --continuous
        Par défaut, ce script s'arrête à la première commande qui retourne avec
        une code d'erreur. Avec cette option, ce script ne s'arrête jamais, bien
        qu'il retourne toujours un code d'erreur si une erreur s'est produite.
    -k, --stopec EXITCODE[=101]
        Spécifier un code d'erreur spécial qui arrête ce script sans erreur, ou
        '' pour désactiver cette fonctionnalité. Ceci permet en début de script
        de faire des tests par exemple sur l'environnement avant de lancer les
        scripts planifiés. Si l'environnement ne convient pas, il suffit au
        script de contrôle de retourner le code d'erreur spécifique pour arrêter
        le traitement."
}

MY_CTLINE="* * * * * $script"
LOCKDELAY=8

source "$(dirname "$0")/ulib/ulib" &&
urequire DEFAULTS crontab ||
exit 1

action=run
lockfile=auto
lockdelay=
fake=
continuous=
stopec=101
parse_opts "${PRETTYOPTS[@]}" \
    --help '$exit_with display_help' \
    --add,--install action=install \
    --remove,--uninstall action=uninstall \
    --lock: lockfile= \
    --lockdelay: lockdelay= \
    -n,--fake fake=1 \
    -c,--continuous continuous=1 \
    -k:,--stop: stopec=1 \
    @ args -- "$@" && set -- "${args[@]}" || die "$args"

crontab="$1"; shift
[ -n "$crontab" ] || die_with "Vous devez spécifier le fichier crontab" display_help
[ -f "$crontab" ] || die "$crontab: fichier introuvable"
crontab="$(abspath "$crontab")"

if [ "$action" == "install" ]; then
    enable_in_crontab "$MY_CTLINE $(quoted_arg "$crontab")" && estep "add_to_crontab $MY_CTLINE $(quoted_arg "$crontab")"

elif [ "$action" == "uninstall" ]; then
    remove_from_crontab "$MY_CTLINE $(quoted_arg "$crontab")" && estep "remove_from_crontab $MY_CTLINE $(quoted_arg "$crontab")"

elif [ "$action" == "run" ]; then
    if [ "$lockfile" == auto ]; then
        if is_root; then
            lockfile="/var/run/$scriptname$crontab.lock"
            mkdirof "$lockfile" || die
        else
            lockfile=
        fi
    fi
    [ -n "$lockdelay" ] || lockdelay="$LOCKDELAY"

    if [ -n "$lockfile" ]; then
        lockwarn="${lockfile%.lock}.lockwarn"
        retry=1
        while [ -n "$retry" ]; do
            case "$(lf_trylock -h "$lockdelay" "$lockfile")" in
            locked)
                edebug "Arrêt du script parce que le verrou $lockfile est posé"
                exit 0
                ;;
            stale)
                msg="Un verrou sur '$scriptname $crontab' est posé depuis plus de $lockdelay heures. Veuillez faire vos vérification et supprimer le cas échéant le fichier $lockfile"
                logger -p cron.warn -t "$scriptname" -- "$msg"
                if [ -f "$lockwarn" ]; then
                    edebug "$msg"
                else
                    touch "$lockwarn"
                    ewarn "$msg"
                fi
                exit 1
                ;;
            retry) :;;
            *) retry=;;
            esac
        done
        [ -f "$lockwarn" ] && rm "$lockwarn"
        autoclean "$lockfile"
    fi

    function __ctexec() {
        local ec=0
        if [ -n "$fake" ]; then
            echo "$*"
        else
            edebug "$*"
            (eval "$*"); ec=$?
        fi
        [ -n "$stopec" -a "$ec" == "$stopec" ] && exit 0
        [ -z "$continuous" -a "$ec" != 0 ] && exit "$ec"
        return 0
    }
    function __cterror() {
        die "$*"
    }
    ctscript="$(ctresolve <"$crontab")"
    ec=0
    edebug "$ctscript"
    (eval "$ctscript"); ec=$?
    [ -f "$lockfile" ] && rm "$lockfile"
    exit "$ec"
fi