770 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			770 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
#!/bin/bash
 | 
						|
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
 | 
						|
source "$(dirname "$0")/lib/ulib/ulib" || exit 1
 | 
						|
urequire DEFAULTS crontab install
 | 
						|
 | 
						|
function display_help() {
 | 
						|
    uecho "$scriptname: lancer une suite de commande en respectant une planification de type cron
 | 
						|
 | 
						|
USAGE
 | 
						|
    $scriptname [options] [/path/to/uscrontab] [var=value...]
 | 
						|
    $scriptname -e [/path/to/uscrontab]
 | 
						|
    $scriptname -l
 | 
						|
 | 
						|
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.
 | 
						|
 | 
						|
Avec la première forme du script, le fichier spécifié est traité. Si aucun
 | 
						|
fichier n'est spécifié, fusionner s'il existe le fichier
 | 
						|
    $USCRONTAB_USERFILE
 | 
						|
avec chacun des fichiers du répertoire
 | 
						|
    $USCRONTAB_USERDIR
 | 
						|
dans un fichier temporaire, puis exécuter le fichier résultat avec le nom
 | 
						|
virtuel
 | 
						|
    $USCRONTAB_USER
 | 
						|
note: le nom virtuel est utilisé pour le verrouillage avec --lock
 | 
						|
 | 
						|
A chaque lancement de ce script, le fichier /path/to/uscrontab spécifié est
 | 
						|
examiné pour déterminer quels commandes doivent être exécutées. 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 l'exécution, elles
 | 
						|
sont garanties de s'exécuter dans l'ordre du fichier, l'une après l'autre.
 | 
						|
 | 
						|
Les définitions var=value mentionnées sur la ligne de commande sont des
 | 
						|
définitions de variables à effectuer avant de lancer les commandes.
 | 
						|
 | 
						|
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\"
 | 
						|
 | 
						|
    La syntaxe ?= permet de définir la valeur d'une variable si elle n'est pas
 | 
						|
    déjà définie:
 | 
						|
 | 
						|
        var?=default
 | 
						|
 | 
						|
    Les lignes de la forme suivante permettent d'exécuter une commande qui est
 | 
						|
    exécutée systématiquement et ignore la planification:
 | 
						|
 | 
						|
        \$one-line-command
 | 
						|
 | 
						|
    Une variante permet de spécifier des commandes sur plusieurs lignes.
 | 
						|
    ATTENTION! \${ et $} doivent être tous seuls sur la ligne.
 | 
						|
 | 
						|
        \${
 | 
						|
        several
 | 
						|
        commands
 | 
						|
        ...
 | 
						|
        $}
 | 
						|
 | 
						|
    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
 | 
						|
 | 
						|
    Le code d'erreur de ces commandes est ignoré, contrairement à ce qui se
 | 
						|
    passe pour les commandes qui font l'objet d'une planification.
 | 
						|
 | 
						|
== 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
 | 
						|
 | 
						|
    Les deux formats peuvent être utilisés ensemble. Par exemple les lignes
 | 
						|
    suivantes exécutent command1 et command2 toutes les heures ET à 1h05:
 | 
						|
 | 
						|
        0 * * * *
 | 
						|
        5 1 * * * command1
 | 
						|
                  command2
 | 
						|
 | 
						|
    Une dernière forme existe, qui ne matche QUE s'il y a au moins une autre
 | 
						|
    planification de type crontab qui matche aussi:
 | 
						|
 | 
						|
        @any command
 | 
						|
 | 
						|
    Ce format peut être utilisé pour initialiser l'environnement avant de lancer
 | 
						|
    les commandes planifiées.
 | 
						|
 | 
						|
    Par défaut, le script s'arrête à la première commande planifiée qui retourne
 | 
						|
    avec un code d'erreur. Il est possible d'ignorer le code d'erreur d'une
 | 
						|
    commande avec nostop, e.g:
 | 
						|
 | 
						|
        0 * * * * nostop command
 | 
						|
 | 
						|
    Cf aussi l'option --continuous pour modifier le comportement par défaut
 | 
						|
 | 
						|
== 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 par un processus externe pour
 | 
						|
      empêcher par exemple de mettre à jour les scripts pendant qu'il sont en
 | 
						|
      train de tourner.
 | 
						|
      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.
 | 
						|
 | 
						|
    La fonction elogto() permet de spécifier un fichier vers lequel toutes les
 | 
						|
    sorties sont redirigées.
 | 
						|
 | 
						|
    La fonction osvar() permet d'utiliser une variable de type oneshot définie
 | 
						|
    via l'option --osvar. Dans l'exemple suivant:
 | 
						|
 | 
						|
        0 1 * * *
 | 
						|
          osvar FIRST=default SECOND
 | 
						|
          echo \"first is \$FIRST and second is \$SECOND\"
 | 
						|
 | 
						|
    En temps normal, FIRST vaut la valeur par défaut 'default' et SECOND vaut la
 | 
						|
    chaine vide ''. Si on appelle '$scriptname -v FIRST=1 -v SECOND=2' alors ces
 | 
						|
    variables auront la valeur spécifiée *une seule fois* à la prochaine
 | 
						|
    invocation avant de retrouver leur valeur par défaut à l'invocation
 | 
						|
    suivante.
 | 
						|
 | 
						|
OPTIONS
 | 
						|
    -A, --install
 | 
						|
        Installer une planification toutes les minutes du script dans la crontab
 | 
						|
        de l'utilisateur. Si l'argument /path/to/uscrontab n'est pas spécifié,
 | 
						|
        c'est une planification générique qui exécute les fichiers par défaut
 | 
						|
        qui est installée.
 | 
						|
    -R, --uninstall
 | 
						|
        Désinstaller la planification toutes les minutes du script de la crontab
 | 
						|
        de l'utilisateur. Si l'argument /path/to/uscrontab est spécifié, cette
 | 
						|
        instance est désinstallée. Sinon, ne désinstaller que la planification
 | 
						|
        générique.
 | 
						|
    -d, --disable-only
 | 
						|
        Avec l'option -R, désactiver la planification au lieu de la supprimer.
 | 
						|
    -e, --edit
 | 
						|
        Lancer un editeur pour modifier l'uscrontab spécifiée. Si aucun fichier
 | 
						|
        n'est spécifié, éditer $USCRONTAB_USERFILE
 | 
						|
    -a, --add
 | 
						|
        Installer un script uscrontab dans le répertoire approprié. L'argument
 | 
						|
        doit être de la forme [name:]/path/to/uscrontab
 | 
						|
        Si name n'est pas spécifié, le nom de base du fichier spécifié est
 | 
						|
        utilisé. Si name est vide ou vaut \$USER (soit $USER en l'occurence),
 | 
						|
        copier le fichier spécifié vers le chemin $USCRONTAB_USERFILE
 | 
						|
        Sinon, copier le fichier spécifié vers $USCRONTAB_USERDIR/name
 | 
						|
    -r, --remove
 | 
						|
        Supprimer le script uscrontab spécifié. L'argument doit être le nom du
 | 
						|
        script à supprimer.  Si l'argument n'est pas spécifié ou vaut \$USER
 | 
						|
        (soit $USER en l'occurence), supprimer le fichier $USCRONTAB_USERFILE
 | 
						|
        s'il existe
 | 
						|
    -l, --list
 | 
						|
        Si l'argument /path/to/crontab est spécifié, afficher le contenu de ce
 | 
						|
        fichier. Sinon, lister les contenus des fichiers crontab qui sont
 | 
						|
        exécutés avec la planification actuelle. Si une planification générique
 | 
						|
        est installée, ou si aucune planification n'est en cours, afficher le
 | 
						|
        contenu du fichier
 | 
						|
            $USCRONTAB_USERFILE
 | 
						|
        et chacun des fichiers du répertoire
 | 
						|
            $USCRONTAB_USERDIR
 | 
						|
    -N, --nop
 | 
						|
        Ne pas examiner la planification. Traiter uniquement l'option --osvar le
 | 
						|
        cas échéant.
 | 
						|
    -n, --fake
 | 
						|
        Afficher au lieu de les exécuter les commandes qui doivent être lancées
 | 
						|
    -P, --pause-for NBMINS
 | 
						|
        Désactiver les planifications pendant NBMINS minutes. Utiliser -1 pour
 | 
						|
        désactiver les planifications sans limite de durée. Pendant la période
 | 
						|
        de pause, toutes les invocations de uscrontab n'ont aucun effet, sauf si
 | 
						|
        on utilise l'option --force
 | 
						|
    -p, --pause
 | 
						|
        Désactiver les planifications pendant 1 journée. Equivalent à -P 1440
 | 
						|
    -Y, --unpause
 | 
						|
        Réactiver les planifications après une mise en pause
 | 
						|
    -W, --pwait-for NBMINS
 | 
						|
        Mettre les planifications en pause pendant 1 journée, puis attendre la
 | 
						|
        fin de la planification actuellement en cours d'exécution pendant au
 | 
						|
        maximum NBMIN minutes. Utiliser -1 pour attendre sans limitation de
 | 
						|
        durée.
 | 
						|
    -w, --pwait
 | 
						|
        Equivalent à -W 180 soit une attente de 30 minutes maximum.
 | 
						|
    -f, --force
 | 
						|
        Forcer l'exécution de la planification, même si elle a été mise en pause
 | 
						|
        avec l'option --pause
 | 
						|
    -v, --osvar NAME[=VALUE]
 | 
						|
        Définir une variable de type oneshot. Par défaut, VALUE vaut 1 si elle
 | 
						|
        n'est pas spécifiée.
 | 
						|
 | 
						|
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 consigné 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 planifiée qui
 | 
						|
        retourne avec un code d'erreur. Notez que les codes d'erreur des
 | 
						|
        commandes sans planification sont toujours ignorés. 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.  Il est possible d'ignorer le
 | 
						|
        code d'erreur pour une commande en particulier avec le préfixe nostop
 | 
						|
    -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.
 | 
						|
    --show-ctnow
 | 
						|
        Afficher l'heure de référence au format crontab 'min hou day mon dow'
 | 
						|
        Cette valeur peut être utilisée avec l'option --force-ctnow dans des
 | 
						|
        tests pour reproduire une condition spécifique.
 | 
						|
    --force-ctnow 'min hou day mon dow'
 | 
						|
        Pour le développement ou des tests, forcer la valeur de l'heure de
 | 
						|
        référence. Il faut respecter le format, sinon les résultats ne sont pas
 | 
						|
        garantis. Le mieux est de reprendre le résultat de l'option --show-ctnow
 | 
						|
        en le modifiant un peu si nécessaire.
 | 
						|
    -@, --hm H[:M]
 | 
						|
    -H, --h00
 | 
						|
    -M, --h30
 | 
						|
        Options pour simplifier l'utilisation de --force-ctnow. Supposons que
 | 
						|
        --show-ctnow affiche 'Y X day mon dow', alors:
 | 
						|
            -@ H:M  | est équivalent à  |  --force-ctnow 'M H day mon dow'
 | 
						|
            -H      |        ''         |  --force-ctnow '0 X day mon dow'
 | 
						|
            -M      |        ''         |  --force-ctnow '30 X day mon dow'
 | 
						|
    -G, --any-ctnow
 | 
						|
        Pour le développement ou des tests, lancer toutes les commandes dans
 | 
						|
        l'ordre sans tenir compte de l'heure de référence. Cette commande ne
 | 
						|
        devrait pas être utilisée en temps normal, mais elle existe pour
 | 
						|
        simplifier les tests avec --show-ctnow + --force-ctnow dans les cas
 | 
						|
        simples."
 | 
						|
}
 | 
						|
 | 
						|
function set_usercrontabs() {
 | 
						|
    # initialiser le tableau $1(=usercrontabs) avec la liste suivante: le
 | 
						|
    # fichier $USCRONTAB_USERFILE s'il existe, puis la liste des fichiers dans
 | 
						|
    # le répertoire $USCRONTAB_USERDIR
 | 
						|
    local -a _userfile _userdir
 | 
						|
    [ -f "$USCRONTAB_USERFILE" ] && _userfile=("$USCRONTAB_USERFILE")
 | 
						|
    array_lsfiles _userdir "$USCRONTAB_USERDIR"
 | 
						|
    eval "${1:-usercrontabs}"'=("${_userfile[@]}" "${_userdir[@]}")'
 | 
						|
}
 | 
						|
 | 
						|
function get_usercrontab() {
 | 
						|
    # trouver le chemin correspondant au crontab $1. Si $1 n'est pas spécifié ou
 | 
						|
    # vaut $USER, afficher $USCRONTAB_USERFILE, sinon afficher $USCRONTAB_USERDIR/$1
 | 
						|
    local name="$1"
 | 
						|
    [ "$name" == "$USER" ] && name=
 | 
						|
    if [ -n "$name" ]; then
 | 
						|
        echo "$USCRONTAB_USERDIR/$name"
 | 
						|
    else
 | 
						|
        echo "$USCRONTAB_USERFILE"
 | 
						|
    fi
 | 
						|
}
 | 
						|
 | 
						|
function check_pause() {
 | 
						|
    # retourner 0 si on est en pause
 | 
						|
    local pauseuntil pausefile="$pausefile" now="$2"
 | 
						|
    if [ -f "$pausefile" ]; then
 | 
						|
        pauseuntil="$(<"$pausefile")"
 | 
						|
        if [ -z "$pauseuntil" ]; then
 | 
						|
            # pause sans limitation de durée
 | 
						|
            return 0
 | 
						|
        elif [ "$pauseuntil" -gt "$now" ]; then
 | 
						|
            return 0
 | 
						|
        fi
 | 
						|
    fi
 | 
						|
    return 1
 | 
						|
}
 | 
						|
 | 
						|
USCRONTAB_CTLINE="* * * * * $script"
 | 
						|
USCRONTAB_LOCKDELAY=8
 | 
						|
USCRONTAB_STOPEC=101
 | 
						|
USCRONTAB_BASEDIR=/var/local/uscrontab
 | 
						|
USCRONTAB_USERFILE="$USCRONTAB_BASEDIR/users/$USER"
 | 
						|
USCRONTAB_USERDIR="$USCRONTAB_BASEDIR/users.d/$USER"
 | 
						|
USCRONTAB_USER="$USCRONTAB_BASEDIR/$USER"
 | 
						|
 | 
						|
action=run
 | 
						|
disable_only=
 | 
						|
lockfile=auto
 | 
						|
lockdelay=
 | 
						|
fake=
 | 
						|
continuous=
 | 
						|
force_ctnow=
 | 
						|
pause=
 | 
						|
pwait=
 | 
						|
force=
 | 
						|
osvars=()
 | 
						|
parse_opts "${PRETTYOPTS[@]}" \
 | 
						|
    --help '$exit_with display_help' \
 | 
						|
    -A,--add,--install action=install \
 | 
						|
    -R,--remove,--uninstall action=uninstall \
 | 
						|
    -d,--disable-only '$action=uninstall; disable_only=1' \
 | 
						|
    -e,--edit action=edit \
 | 
						|
    -a,--add action=add \
 | 
						|
    -r,--remove action=remove \
 | 
						|
    -l,--list action=list \
 | 
						|
    -N,--nop action=nop \
 | 
						|
    -n,--fake fake=1 \
 | 
						|
    -P:,--pause-for: '$action=pause; set@ pause' \
 | 
						|
    -p,--pause action=pause \
 | 
						|
    -Y,--unpause action=unpause \
 | 
						|
    -W:,--pwait-for: '$action=pwait; set@ pwait' \
 | 
						|
    -w,--pwait action=pwait \
 | 
						|
    -f,--force force=1 \
 | 
						|
    -v:,--osvar: osvars \
 | 
						|
    --lock: lockfile= \
 | 
						|
    --lockdelay: lockdelay= \
 | 
						|
    -c,--continuous continuous=1 \
 | 
						|
    -k:,--stop: USCRONTAB_STOPEC= \
 | 
						|
    --show-ctnow action=show-ctnow \
 | 
						|
    --force-ctnow: __CTRESOLVE_CTNOW= \
 | 
						|
    -@:,--hm: force_ctnow= \
 | 
						|
    -H,--h00 force_ctnow=h00 \
 | 
						|
    -M,--h30 force_ctnow=h30 \
 | 
						|
    -G,--any-ctnow __CTRESOLVE_CTNOW="**ANY**" \
 | 
						|
    @ args -- "$@" && set -- "${args[@]}" || die "$args"
 | 
						|
 | 
						|
pausefile="$USCRONTAB_USERFILE.pauseuntil"
 | 
						|
now="$(awk 'BEGIN { print int(systime() / 60) * 60 }')"
 | 
						|
 | 
						|
# ne prendre le premier argument comme un fichier que s'il existe ou s'il n'est
 | 
						|
# pas de la forme *=*
 | 
						|
if [[ "$1" == *=* ]]; then
 | 
						|
    if [ -f "$1" ]; then
 | 
						|
        uscrontab="$1"; shift
 | 
						|
    fi
 | 
						|
else
 | 
						|
    uscrontab="$1"; shift
 | 
						|
fi
 | 
						|
 | 
						|
# écrire les variables oneshot
 | 
						|
for osvar in "${osvars[@]}"; do
 | 
						|
    if [[ "$osvar" == *=* ]]; then
 | 
						|
        splitvar "$osvar" name value
 | 
						|
    else
 | 
						|
        name="$osvar"
 | 
						|
        value=1
 | 
						|
    fi
 | 
						|
    name="${name##*/}" # au cas où...
 | 
						|
    echo -n "$value" >"$USCRONTAB_USERFILE.$name.osvar"
 | 
						|
done
 | 
						|
 | 
						|
if [ "$action" == "pause" ]; then
 | 
						|
    [ -n "$pause" ] || pause=1440
 | 
						|
    if [ "$pause" -lt 0 ]; then
 | 
						|
        pauseuntil=
 | 
						|
    else
 | 
						|
        pauseuntil=$(($now + $pause * 60))
 | 
						|
    fi
 | 
						|
    echo "$pauseuntil" >"$pausefile" || die
 | 
						|
    enote "Désactivation des planifications pour $pause minute(s)"
 | 
						|
    exit 0
 | 
						|
 | 
						|
elif [ "$action" == "unpause" ]; then
 | 
						|
    if [ -f "$pausefile" ]; then
 | 
						|
        rm "$pausefile" || die
 | 
						|
        enote "Réactivation des planifications"
 | 
						|
    fi
 | 
						|
    exit 0
 | 
						|
 | 
						|
elif [ "$action" == "edit" ]; then
 | 
						|
    if [ -z "$uscrontab" ]; then
 | 
						|
        basedir="$(dirname "$USCRONTAB_USERFILE")"
 | 
						|
        [ -d "$basedir" ] || die "$basedir: ce répertoire n'existe pas. Vérifiez l'installation de nutools"
 | 
						|
        uscrontab="$USCRONTAB_USERFILE"
 | 
						|
    fi
 | 
						|
    enote "Edition de $uscrontab"
 | 
						|
    if [ ! -f "$uscrontab" ]; then
 | 
						|
        touch "$uscrontab" || die
 | 
						|
        chmod 640 "$uscrontab"
 | 
						|
    fi
 | 
						|
    "${EDITOR:-vi}" "$uscrontab" || die
 | 
						|
    exit 0
 | 
						|
 | 
						|
elif [ "$action" == "add" ]; then
 | 
						|
    [ -n "$uscrontab" ] || die "Vous devez spécifier le fichier à installer"
 | 
						|
    if [[ "$uscrontab" == *:* ]]; then
 | 
						|
        splitfsep2 "$uscrontab" : name uscrontab
 | 
						|
        [ -f "$uscrontab" ] || die "$uscrontab: fichier introuvable"
 | 
						|
    else
 | 
						|
        name="$(basename "$uscrontab")"
 | 
						|
    fi
 | 
						|
    dest="$(get_usercrontab "$name")"
 | 
						|
 | 
						|
    destdir="$(dirname -- "$dest")"
 | 
						|
    if [ ! -d "$destdir" ]; then
 | 
						|
        mkdir -p "$destdir" || die
 | 
						|
        chmod 750 "$destdir"
 | 
						|
    fi
 | 
						|
 | 
						|
    copy_update_ask "$uscrontab" "$dest"
 | 
						|
    exit 0
 | 
						|
 | 
						|
elif [ "$action" == "remove" ]; then
 | 
						|
    uscrontab="$(get_usercrontab "$uscrontab")"
 | 
						|
    if [ -f "$uscrontab" ]; then
 | 
						|
        ask_yesno "Voulez-vous supprimer le fichier $uscrontab?" C || die
 | 
						|
        enote "Suppression de $uscrontab"
 | 
						|
        rm "$uscrontab" || die
 | 
						|
    fi
 | 
						|
    exit 0
 | 
						|
 | 
						|
elif [ "$action" == "list" ]; then
 | 
						|
    if [ -n "$uscrontab" ]; then
 | 
						|
        uscrontab="$(abspath "$uscrontab")"
 | 
						|
        array_from_lines ctfiles "$(crontab -l 2>/dev/null | awkrun script="$script" uscrontab="$uscrontab" '$6 == script && $7 == uscrontab { print $7 }')"
 | 
						|
        if [ ${#ctfiles[*]} -eq 0 ]; then
 | 
						|
            ewarn "$(ppath "$uscrontab"): non planifié"
 | 
						|
            ctfiles=("$uscrontab")
 | 
						|
        fi
 | 
						|
    else
 | 
						|
        array_from_lines ctfiles "$(crontab -l 2>/dev/null | awkrun script="$script" '$6 == script { if ($7) print $7; else if ($0 ~ /^#/) print "#GENERIC"; else print "GENERIC" }')" #'
 | 
						|
        if array_contains ctfiles "GENERIC"; then
 | 
						|
            # il y a une planification générique
 | 
						|
            array_del ctfiles "GENERIC"
 | 
						|
            set_usercrontabs usercrontabs
 | 
						|
            array_extend ctfiles usercrontabs
 | 
						|
        elif array_contains ctfiles "#GENERIC"; then
 | 
						|
            # il y a une planification générique désactivée
 | 
						|
            array_del ctfiles "#GENERIC"
 | 
						|
            set_usercrontabs usercrontabs
 | 
						|
            array_extend ctfiles usercrontabs
 | 
						|
            ewarn "Planification générique désactivée. Vous pouvez la réactiver avec $scriptname -A"
 | 
						|
        elif [ ${#ctfiles[*]} -eq 0 ]; then
 | 
						|
            einfo "Aucune planification en cours"
 | 
						|
            set_usercrontabs ctfiles
 | 
						|
        fi
 | 
						|
    fi
 | 
						|
 | 
						|
    r=1
 | 
						|
    for ctfile in "${ctfiles[@]}"; do
 | 
						|
        r=0 # il y a au moins une planification
 | 
						|
        etitle "$(ppath "$ctfile")" \
 | 
						|
            cat "$ctfile"
 | 
						|
    done
 | 
						|
 | 
						|
    if check_pause "$pausefile" "$now"; then
 | 
						|
        ewarn "En pause. Réactiver avec $scriptname -Y"
 | 
						|
    fi
 | 
						|
    exit $r
 | 
						|
 | 
						|
elif [ "$action" == "show-ctnow" ]; then
 | 
						|
    ctnow "$now"
 | 
						|
    exit 0
 | 
						|
 | 
						|
elif [ "$action" == "nop" ]; then
 | 
						|
    exit 0
 | 
						|
fi
 | 
						|
 | 
						|
[ -z "$uscrontab" -o -f "$uscrontab" ] || die "$uscrontab: fichier introuvable"
 | 
						|
[ -n "$uscrontab" ] && uscrontab="$(abspath "$uscrontab")"
 | 
						|
 | 
						|
if [ "$action" == "install" ]; then
 | 
						|
    ctline="$USCRONTAB_CTLINE"
 | 
						|
    [ -n "$uscrontab" ] && ctline="$ctline $(qvalm "$uscrontab")"
 | 
						|
    enable_in_crontab "$ctline" && estep "add_to_crontab $ctline"
 | 
						|
 | 
						|
elif [ "$action" == "uninstall" ]; then
 | 
						|
    ctline="$USCRONTAB_CTLINE"
 | 
						|
    [ -n "$uscrontab" ] && ctline="$ctline $(qvalm "$uscrontab")"
 | 
						|
    if [ -n "$disable_only" ]; then
 | 
						|
        disable_in_crontab "$ctline" && estep "disable_in_crontab $ctline"
 | 
						|
    else
 | 
						|
        remove_from_crontab "$ctline" && estep "remove_from_crontab $ctline"
 | 
						|
    fi
 | 
						|
 | 
						|
elif [ "$action" == "run" ]; then
 | 
						|
    if [ -n "$force" ]; then
 | 
						|
        # ne pas tenir compte de l'état de pause
 | 
						|
        :
 | 
						|
    elif check_pause "$pausefile" "$now"; then
 | 
						|
        isatty && ewarn "En pause. Réactiver avec $scriptname -Y"
 | 
						|
        ac_clean "$lockfile"
 | 
						|
        exit 0
 | 
						|
    elif [ -f "$pausefile" ]; then
 | 
						|
        rm "$pausefile"
 | 
						|
    fi
 | 
						|
 | 
						|
    if [ -n "$uscrontab" ]; then
 | 
						|
        default_lockfile="/var/run/$scriptname$uscrontab.lock"
 | 
						|
        lockdesc="$scriptname $uscrontab"
 | 
						|
    else
 | 
						|
        set_usercrontabs usercrontabs
 | 
						|
        ac_set_tmpfile uscrontab
 | 
						|
        for usercrontab in "${usercrontabs[@]}"; do
 | 
						|
            echo "# $usercrontab" >>"$uscrontab"
 | 
						|
            cat "$usercrontab" >>"$uscrontab"
 | 
						|
        done
 | 
						|
        default_lockfile="/var/run/$scriptname$USCRONTAB_USER.lock"
 | 
						|
        lockdesc="$scriptname $USCRONTAB_USER"
 | 
						|
    fi
 | 
						|
 | 
						|
    if [ "$lockfile" == auto ]; then
 | 
						|
        if is_root; then
 | 
						|
            lockfile="$default_lockfile"
 | 
						|
            mkdirof "$lockfile" || die
 | 
						|
        else
 | 
						|
            lockfile=
 | 
						|
        fi
 | 
						|
    fi
 | 
						|
    [ -n "$lockdelay" ] || lockdelay="$USCRONTAB_LOCKDELAY"
 | 
						|
 | 
						|
    if [ -n "$lockfile" ]; then
 | 
						|
        lastmsgfile="${lockfile%.lock}.lastmsg"
 | 
						|
        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 '$lockdesc' est posé depuis plus de $lockdelay heures. Veuillez faire vos vérification et supprimer le cas échéant le fichier $lockfile"
 | 
						|
                lastmsg=
 | 
						|
                [ -f "$lastmsgfile" ] && lastmsg="$(<"$lastmsgfile")" || lastmsg=
 | 
						|
 | 
						|
                logger -p cron.warn -t "$scriptname" -- "$msg"
 | 
						|
                if [ "$msg" != "$lastmsg" ]; then
 | 
						|
                    ewarn "$msg"
 | 
						|
                    echo "$msg" >"$lastmsgfile"
 | 
						|
                else
 | 
						|
                    edebug "$msg"
 | 
						|
                fi
 | 
						|
                exit 1
 | 
						|
                ;;
 | 
						|
            retry) :;;
 | 
						|
            *) retry=;;
 | 
						|
            esac
 | 
						|
        done
 | 
						|
 | 
						|
        rm -f "$lastmsgfile" 2>/dev/null
 | 
						|
        autoclean "$lockfile"
 | 
						|
    fi
 | 
						|
 | 
						|
    function __should_fork() {
 | 
						|
        # tester si la commande doit être lancée dans un sous-shell
 | 
						|
        local cmd
 | 
						|
        cmd="${1%% *}"
 | 
						|
        case "$cmd" in
 | 
						|
        check_pidfile|remove_pidfile|osvar|elogto) return 1;;
 | 
						|
        esac
 | 
						|
        return 0
 | 
						|
    }
 | 
						|
    function __ctexec() {
 | 
						|
        local ec=0
 | 
						|
 | 
						|
        local cmd="${1%% *}"
 | 
						|
        if [ "$cmd" == nostop ]; then
 | 
						|
            cmd="${1#nostop }"
 | 
						|
            shift
 | 
						|
            set -- "$cmd" "$@"
 | 
						|
            local continuous=1
 | 
						|
        fi
 | 
						|
 | 
						|
        if [ -n "$fake" ]; then
 | 
						|
            echo "$*"
 | 
						|
        else
 | 
						|
            edebug "$*"
 | 
						|
            if __should_fork "$1"; then
 | 
						|
                (eval "$*"); ec=$?
 | 
						|
            else
 | 
						|
                 # cas particulier: certaines fonctions doivent être exécutée
 | 
						|
                 # dans le contexte courant, et non pas dans un sous-shell
 | 
						|
                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 "$*"
 | 
						|
    }
 | 
						|
 | 
						|
    if [ -n "$force_ctnow" ]; then
 | 
						|
        ctnow=($(ctnow "$now"))
 | 
						|
        case "$force_ctnow" in
 | 
						|
        h00) ctnow[0]=0;;
 | 
						|
        h30) ctnow[0]=30;;
 | 
						|
        *)
 | 
						|
            force_ctnow="${force_ctnow//./:}" # autoriser H.M en plus de H:M
 | 
						|
            splitpair "$force_ctnow" h m
 | 
						|
            ctnow[0]="${m:-0}"
 | 
						|
            ctnow[1]="$h"
 | 
						|
            ;;
 | 
						|
        esac
 | 
						|
        __CTRESOLVE_CTNOW="${ctnow[*]}"
 | 
						|
    fi
 | 
						|
 | 
						|
    __ctscript="$(ctresolve <"$uscrontab")"
 | 
						|
    ec=0
 | 
						|
    edebug "$__ctscript"
 | 
						|
    (
 | 
						|
        # tableau des fichiers de pid en cours. la conséquence est que ce n'est
 | 
						|
        # pas une erreur d'appeler à plusieurs reprises check_pidfile avec le
 | 
						|
        # même fichier
 | 
						|
        __USCRONTAB_PIDFILES=()
 | 
						|
        function check_pidfile() {
 | 
						|
            if [ -n "$1" ]; then
 | 
						|
                local pidfile="$(abspath "$1")"
 | 
						|
                if ! array_contains __USCRONTAB_PIDFILES "$pidfile"; then
 | 
						|
                    local status
 | 
						|
                    pidfile_set -r "$pidfile"; 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 $(<"$pidfile")
 | 
						|
puis supprimez le cas échéant le fichier $pidfile"
 | 
						|
                        return "$USCRONTAB_STOPEC"
 | 
						|
                        ;;
 | 
						|
                    10)
 | 
						|
                        die "Une erreur s'est produite pendant l'écriture du fichier de pid. Impossible de continuer"
 | 
						|
                        ;;
 | 
						|
                    esac
 | 
						|
                    array_add __USCRONTAB_PIDFILES "$pidfile"
 | 
						|
                fi
 | 
						|
            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() {
 | 
						|
            if [ -n "$1" ]; then
 | 
						|
                local pidfile="$(abspath "$1")"
 | 
						|
                ac_clean "$pidfile"
 | 
						|
                array_del __USCRONTAB_PIDFILES "$pidfile"
 | 
						|
            fi
 | 
						|
        }
 | 
						|
        function osvar() {
 | 
						|
            local __name __value
 | 
						|
            for __name in "$@"; do
 | 
						|
                splitvar "$__name" __name __value
 | 
						|
                if [ -f "$USCRONTAB_USERFILE.$__name.osvar" ]; then
 | 
						|
                    __value="$(<"$USCRONTAB_USERFILE.$__name.osvar")"
 | 
						|
                    rm "$USCRONTAB_USERFILE.$__name.osvar"
 | 
						|
                fi
 | 
						|
                _setv "$__name" "$__value"
 | 
						|
            done
 | 
						|
        }
 | 
						|
 | 
						|
        __ac_forgetall
 | 
						|
        # Si des variables sont spécifiées, les initialiser avant de lancer le
 | 
						|
        # script
 | 
						|
        for __var in "$@"; do
 | 
						|
            splitvar "$__var" __name __value
 | 
						|
            edebug "$__name=$__value"
 | 
						|
            _setv "$__name" "$__value"
 | 
						|
        done
 | 
						|
        eval "$__ctscript"
 | 
						|
        ac_cleanall
 | 
						|
    ); ec=$?
 | 
						|
 | 
						|
    ac_clean "$uscrontab"
 | 
						|
    ac_clean "$lockfile"
 | 
						|
 | 
						|
    exit "$ec"
 | 
						|
fi
 |