#!/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

    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.

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, --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
    -Y, --unpause
        Réactiver les planifications après une mise en pause
    -p, --pause
        Désactiver les planifications pendant 1 journée. Equivalent à -P 1440
    -f, --force
        Forcer l'exécution de la planification, même si elle a été mise en pause
        avec l'option --pause

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."
}

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=
pause=
force=
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,--fake fake=1 \
    -P:,--pause-for: '$action=pause; set@ pause' \
    -p,--pause action=pause \
    -Y,--unpause action=unpause \
    -f,--force force=1 \
    --lock: lockfile= \
    --lockdelay: lockdelay= \
    -c,--continuous continuous=1 \
    -k:,--stop: USCRONTAB_STOPEC= \
    --show-ctnow action=show-ctnow \
    --force-ctnow: __CTRESOLVE_CTNOW= \
    @ args -- "$@" && set -- "${args[@]}" || die "$args"

uscrontab="$1"; shift

pausefile="$USCRONTAB_USERFILE.pauseuntil"
now="$(awk 'BEGIN { print int(systime() / 60) * 60 }')"

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 check_pause "$pausefile" "$now"; then
        ewarn "En pause. Réactiver avec $scriptname -Y"
    fi

    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
    exit $r

elif [ "$action" == "show-ctnow" ]; then
    ctnow "$now"
    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 $(quoted_arg "$uscrontab")"
    enable_in_crontab "$ctline" && estep "add_to_crontab $ctline"

elif [ "$action" == "uninstall" ]; then
    ctline="$USCRONTAB_CTLINE"
    [ -n "$uscrontab" ] && ctline="$ctline $(quoted_arg "$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|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 "$*"
    }

    __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
        }

        __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"
            set_var "$__name" "$__value"
        done
        eval "$__ctscript"
        ac_cleanall
    ); ec=$?

    ac_clean "$uscrontab"
    ac_clean "$lockfile"

    exit "$ec"
fi
