344 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			344 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
#!/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
 | 
						|
    $scriptname -l [/path/to/crontab]
 | 
						|
 | 
						|
La première forme du script doit normalement être lancé toutes les minutes par
 | 
						|
une tâche cron. Utiliser l'option --install pour ajouter automatique la ligne
 | 
						|
dans la crontab de l'utilisateur.
 | 
						|
 | 
						|
A chaque lancement du script, il examine quels scripts doivent être exécutés
 | 
						|
dans le fichier crontab spécifié. Ce fichier est composé de lignes dans un
 | 
						|
format particulier, qui sont analysées et traitées dans l'ordre.
 | 
						|
 | 
						|
Quelles que soient les lignes qui sont sélectionnées pour le lancement, elles
 | 
						|
sont garanties de s'exécuter dans l'ordre du fichier, l'une après l'autre.
 | 
						|
 | 
						|
Les lignes commençant par # sont des commentaires et sont ignorées
 | 
						|
 | 
						|
== Définitions de variables et exécution de commandes ==
 | 
						|
 | 
						|
    Les lignes de la forme suivante sont des définitions de variable:
 | 
						|
 | 
						|
        [export] var=\"valeur de la variable\"
 | 
						|
 | 
						|
    Ces lignes sont des définitions de variable bash qui sont exécutées telles
 | 
						|
    quelles. Il n'est donc pas autorisé de mettre des espaces autour de =. Par
 | 
						|
    exemple, les lignes suivantes sont des erreurs de syntaxe:
 | 
						|
 | 
						|
        var = bad
 | 
						|
        var=pas de quotes autour de la valeur
 | 
						|
 | 
						|
    alors que celles-ci sont correctes:
 | 
						|
 | 
						|
        var=ok
 | 
						|
        var=\"valeur avec des espaces\"
 | 
						|
        var='on peut utiliser des quotes aussi'
 | 
						|
 | 
						|
    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
 | 
						|
 | 
						|
    Bien sûr, il ne faut pas oublier de quoter les espaces:
 | 
						|
 | 
						|
        PATH+=\"/path/to/dir with spaces\"
 | 
						|
 | 
						|
    Les lignes de la forme suivante permettent d'exécuter une commande
 | 
						|
    quelconque:
 | 
						|
 | 
						|
        $command-line
 | 
						|
 | 
						|
    Ces commandes sont exécutées systématiquement et ignorent la planification.
 | 
						|
    On peut s'en servir notamment pour lire un fichier de configuration qui
 | 
						|
    définit des variables ou des fonctions:
 | 
						|
 | 
						|
        $source path/to/file
 | 
						|
 | 
						|
== Planification de commandes ==
 | 
						|
 | 
						|
    Les autres lignes doivent être au format d'une ligne de crontab:
 | 
						|
 | 
						|
        minutes hours days months dows command-line
 | 
						|
 | 
						|
    command-line peut être n'importe quelle ligne de commande bash, pourvu
 | 
						|
    qu'elle soit sur une seule ligne.
 | 
						|
 | 
						|
    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, les lignes suivantes permettent d'exécuter
 | 
						|
    'command' toutes les heures ET à 1h05:
 | 
						|
 | 
						|
        0 * * * *
 | 
						|
        5 1 * * * command
 | 
						|
 | 
						|
    Il est aussi possible d'utiliser la même planification pour plusieurs
 | 
						|
    commandes sans devoir répéter la définition de la planification. Les lignes
 | 
						|
    suivantes planifient command1 et command2 toutes les heures:
 | 
						|
 | 
						|
        0 * * * * command1
 | 
						|
                  command2
 | 
						|
 | 
						|
    Pour être prise en compte, la ligne command2 doit commencer par au moins un
 | 
						|
    espace ou une tabulation. Pour la lisibilité, la syntaxe suivante est
 | 
						|
    supportée aussi:
 | 
						|
 | 
						|
        0 * * * *
 | 
						|
          command1
 | 
						|
          command2
 | 
						|
 | 
						|
== Fonctions disponibles ==
 | 
						|
 | 
						|
    La fonction check_pidfile() est disponible, et permet de vérifier qu'une
 | 
						|
    opération n'est pas déjà en cours. Si cette fonction est utilisée, il ne
 | 
						|
    faut pas modifier la valeur de -k. Par exemple:
 | 
						|
 | 
						|
        0 1 * * *
 | 
						|
          check_pidfile /path/to/pid [args]
 | 
						|
          long-running-script
 | 
						|
 | 
						|
    check_pidfile() doit être utilisée toute seule sur la ligne et s'utilise
 | 
						|
    avec les argument suivants:
 | 
						|
 | 
						|
        check_pidfile PIDFILE [DESC] [BARRIER]
 | 
						|
 | 
						|
    - PIDFILE est le fichier de PID qui est vérifié
 | 
						|
    - DESC est la description du traitement qui est effectué. La valeur par
 | 
						|
      défaut est \"Une synchronisation\". Si le fichier de PID est présent, le
 | 
						|
      message suivant est affiché:
 | 
						|
        DESC est en cours.
 | 
						|
        Si vous pensez que c'est une erreur, veuillez vérifier le process de pid PID
 | 
						|
        puis supprimez le cas échéant le fichier PIDFILE
 | 
						|
    - BARRIER est un fichier qui est créé avec le contenu 'PID' s'il n'existe
 | 
						|
      pas encore, et si la vérification du fichier de PID est faite avec succès.
 | 
						|
      La présence de ce fichier peut-être vérifiée pour empêcher par exemple la
 | 
						|
      mise à jour du code de synchronisation. Son contenu peut être examiné pour
 | 
						|
      connaître le PID du processus qui l'a créé initialement. Le fichier est
 | 
						|
      automatiquement supprimé à la fin de ce script.
 | 
						|
      Attention: ce fichier n'est pas un verrou, il peut être supprimé à tout
 | 
						|
      moment. Notamment, si deux scripts sont configurés pour créer le même
 | 
						|
      fichier barrière, le premier script supprimera le fichier barrière avant
 | 
						|
      la fin de l'exécution du second script.
 | 
						|
 | 
						|
    La fonction remove_pidfile() permet de supprimer un fichier de pid pour
 | 
						|
    spécifier qu'une opération est terminée. Considérons l'exemple suivant:
 | 
						|
 | 
						|
        0 1 * * *
 | 
						|
          check_pidfile /path/to/pid
 | 
						|
          script1
 | 
						|
          script2
 | 
						|
          remove_pidfile /path/to/pid
 | 
						|
          script3
 | 
						|
 | 
						|
    Dans cet exemple, il ne faut pas qu'une autre occurence de script1 tourne
 | 
						|
    pendant que script2 tourne. Par contre, plusieurs occurences de script3
 | 
						|
    peuvent tourner en parallèle.
 | 
						|
 | 
						|
OPTIONS
 | 
						|
    -A, --install
 | 
						|
        Installer une planification toutes les minutes de ce script dans la
 | 
						|
        crontab de l'utilisateur. L'argument /path/to/crontab est requis.
 | 
						|
    -R, --uninstall
 | 
						|
        Désinstaller la planification toutes les minutes de ce script du crontab
 | 
						|
        de l'utilisateur. L'argument /path/to/crontab est requis, et seule cette
 | 
						|
        instance est désinstallée le cas échéant.
 | 
						|
    -l, --list
 | 
						|
        Lister les contenus des fichiers crontab dont l'exécution a été
 | 
						|
        planifiée avec --install
 | 
						|
        Si /path/to/crontab est spécifié, ne lister le contenu de ce fichier que
 | 
						|
        si et seulement si son exécution a été planifiée.
 | 
						|
    -n, --fake
 | 
						|
        Afficher au lieu de les exécuter les commandes qui doivent être lancées
 | 
						|
 | 
						|
OPTIONS AVANCEES
 | 
						|
    --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 $USCRONTAB_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[=$USCRONTAB_LOCKDELAY]
 | 
						|
        Changer le nombre d'heures pendant lesquelles on autorise le script a
 | 
						|
        verrouiller l'exécution avant d'afficher un avertissement.
 | 
						|
    -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."
 | 
						|
}
 | 
						|
 | 
						|
source "$(dirname "$0")/ulib/ulib" &&
 | 
						|
urequire DEFAULTS crontab ||
 | 
						|
exit 1
 | 
						|
 | 
						|
USCRONTAB_CTLINE="* * * * * $script"
 | 
						|
USCRONTAB_LOCKDELAY=8
 | 
						|
USCRONTAB_STOPEC=101
 | 
						|
 | 
						|
action=run
 | 
						|
lockfile=auto
 | 
						|
lockdelay=
 | 
						|
fake=
 | 
						|
continuous=
 | 
						|
parse_opts "${PRETTYOPTS[@]}" \
 | 
						|
    --help '$exit_with display_help' \
 | 
						|
    -A,--add,--install action=install \
 | 
						|
    -R,--remove,--uninstall action=uninstall \
 | 
						|
    --lock: lockfile= \
 | 
						|
    --lockdelay: lockdelay= \
 | 
						|
    -n,--fake fake=1 \
 | 
						|
    -c,--continuous continuous=1 \
 | 
						|
    -k:,--stop: USCRONTAB_STOPEC= \
 | 
						|
    -l,--list action=list \
 | 
						|
    @ args -- "$@" && set -- "${args[@]}" || die "$args"
 | 
						|
 | 
						|
if [ "$action" == "list" ]; then
 | 
						|
    crontab="$1"; shift
 | 
						|
    [ -n "$crontab" ] && crontab="$(abspath "$crontab")"
 | 
						|
 | 
						|
    array_from_lines ctfiles "$(crontab -l 2>/dev/null | awkrun script="$script" '$6 == script { print $7 }')"
 | 
						|
    found=
 | 
						|
    for ctfile in "${ctfiles[@]}"; do
 | 
						|
        if [ -z "$crontab" -o "$ctfile" == "$crontab" ]; then
 | 
						|
            found=1
 | 
						|
            etitle "$(ppath "$ctfile")"
 | 
						|
            cat "$ctfile"
 | 
						|
            eend
 | 
						|
        fi
 | 
						|
    done
 | 
						|
    if [ -n "$crontab" -a -z "$found" ]; then
 | 
						|
        ewarn "$(ppath "$crontab"): non planifié"
 | 
						|
    fi
 | 
						|
    exit 0
 | 
						|
fi
 | 
						|
 | 
						|
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 "$USCRONTAB_CTLINE $(quoted_arg "$crontab")" && estep "add_to_crontab $USCRONTAB_CTLINE $(quoted_arg "$crontab")"
 | 
						|
 | 
						|
elif [ "$action" == "uninstall" ]; then
 | 
						|
    remove_from_crontab "$USCRONTAB_CTLINE $(quoted_arg "$crontab")" && estep "remove_from_crontab $USCRONTAB_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="$USCRONTAB_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 "$*"
 | 
						|
            if [ "${1#check_pidfile }" != "$1" ]; then
 | 
						|
                # cas particulier, c'est une fonction à exécuter dans le
 | 
						|
                # contexte courant, et non pas dans un sous-shell
 | 
						|
                eval "$*"; ec=$?
 | 
						|
            else
 | 
						|
                (eval "$*"); ec=$?
 | 
						|
            fi
 | 
						|
        fi
 | 
						|
        [ -n "$USCRONTAB_STOPEC" -a "$ec" == "$USCRONTAB_STOPEC" ] && exit 0
 | 
						|
        [ -z "$continuous" -a "$ec" != 0 ] && exit "$ec"
 | 
						|
        return 0
 | 
						|
    }
 | 
						|
    function __cterror() {
 | 
						|
        die "$*"
 | 
						|
    }
 | 
						|
    ctscript="$(ctresolve <"$crontab")"
 | 
						|
    ec=0
 | 
						|
    edebug "$ctscript"
 | 
						|
    (
 | 
						|
        function check_pidfile() {
 | 
						|
            if [ -n "$1" ]; then
 | 
						|
                local status
 | 
						|
                pidfile_set -r "$1"; status=$?
 | 
						|
                case "$status" in
 | 
						|
                1)
 | 
						|
                    eerror "${2:-Une synchronisation} est en cours.
 | 
						|
Si vous pensez que c'est une erreur, veuillez vérifier le process de pid $(<"$1")
 | 
						|
puis supprimez le cas échéant le fichier $1"
 | 
						|
                    return "$USCRONTAB_STOPEC"
 | 
						|
                    ;;
 | 
						|
                10)
 | 
						|
                    die "Une erreur s'est produite pendant l'écriture du fichier de pid. Impossible de continuer"
 | 
						|
                    ;;
 | 
						|
                esac
 | 
						|
            fi
 | 
						|
            if [ -n "$3" -a -w "$(dirname "$3")" ]; then
 | 
						|
                (set -o noclobber
 | 
						|
                    echo_ $$ >"$3" &&
 | 
						|
                    chmod 644 "$3"
 | 
						|
                ) 2>/dev/null &&
 | 
						|
                autoclean "$3"
 | 
						|
            fi
 | 
						|
            return 0
 | 
						|
        }
 | 
						|
        function remove_pidfile() {
 | 
						|
            [ -n "$1" ] && ac_clean "$1"
 | 
						|
        }
 | 
						|
        __ac_forgetall
 | 
						|
        eval "$ctscript"
 | 
						|
        ac_cleanall
 | 
						|
    ); ec=$?
 | 
						|
    [ -f "$lockfile" ] && rm "$lockfile"
 | 
						|
    exit "$ec"
 | 
						|
fi
 |