From b0f00b2f34fdbe816df09905a7ff897e741eef8e Mon Sep 17 00:00:00 2001 From: Jephte CLAIN Date: Wed, 2 Oct 2013 13:37:10 +0400 Subject: [PATCH] =?UTF-8?q?support=20check=5Fpidfile()=20et=20remove=5Fpid?= =?UTF-8?q?file=20dans=20uscrontab=20support=20de=20l'ex=C3=A9cution=20de?= =?UTF-8?q?=20commandes=20arbitraires?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ulib/.ulib_version | 2 +- ulib/base | 5 +- ulib/crontab | 22 ++++-- uscrontab | 185 +++++++++++++++++++++++++++++++++++++-------- 4 files changed, 173 insertions(+), 41 deletions(-) diff --git a/ulib/.ulib_version b/ulib/.ulib_version index 0cfbf08..00750ed 100644 --- a/ulib/.ulib_version +++ b/ulib/.ulib_version @@ -1 +1 @@ -2 +3 diff --git a/ulib/base b/ulib/base index 437ce0f..fd7b83f 100644 --- a/ulib/base +++ b/ulib/base @@ -3743,13 +3743,14 @@ function simple_menu() { # autoclean: gérer une liste de fichiers temporaires à supprimer en fin de # programme -__ac_files=() +function __ac_forgetall() { __ac_files=(); } +__ac_forgetall function __ac_trap() { local file for file in "${__ac_files[@]}"; do [ -e "$file" ] && rm -rf "$file" 2>/dev/null done - __ac_files=() + __ac_forgetall } trap __ac_trap 1 3 15 EXIT function autoclean() { diff --git a/ulib/crontab b/ulib/crontab index 85d028a..13a93ae 100644 --- a/ulib/crontab +++ b/ulib/crontab @@ -149,12 +149,12 @@ BEGIN { matches = 0 } -/^[a-zA-Z_][a-zA-Z0-9_]*[-+%#]=/ { +/^(export[ \t]+)?[a-zA-Z_][a-zA-Z0-9_]*[-+%#]=/ { # manipulation de variables PATH - match($0, /^([a-zA-Z_][a-zA-Z0-9_]*)([-+%#])=(.*)$/, parts) - name = parts[1] - type = parts[2] - value = parts[3] + match($0, /^(export[ \t]+)?([a-zA-Z_][a-zA-Z0-9_]*)([-+%#])=(.*)$/, parts) + name = parts[2] + type = parts[3] + value = parts[4] if (type == "+" || type == "%") { print "uaddpath " value " " name } else if (type == "#") { @@ -162,14 +162,22 @@ BEGIN { } else if (type == "-") { print "udelpath " value " " name } + print "export " name next } -/^[a-zA-Z_][a-zA-Z0-9_]*=/ { +/^(export[ \t]+)?[a-zA-Z_][a-zA-Z0-9_]*=/ { # spécification de variable + sub(/^export[ \t]+/, "", $0) print "export " $0 next } -/^[ ]*[-0-9/*,]+[ ]*[-0-9/*,]+[ ]*[-0-9/*,]+[ ]*[-0-9/*,]+[ ]*[-0-9/*,]+/ { +/^\$.+/ { + # exécution de commande arbitraire + sub(/^\$/, "", $0) + print + next +} +/^[ \t]*[-0-9/*,]+[ ]*[-0-9/*,]+[ ]*[-0-9/*,]+[ ]*[-0-9/*,]+[ ]*[-0-9/*,]+/ { # ligne de planification M = $1; H = $2; dom = $3; mon = $4; dow = $5 sub(/^[ ]*[^ ]+[ ]+[^ ]+[ ]+[^ ]+[ ]+[^ ]+[ ]+[^ ]+[ ]*/, "", $0) diff --git a/uscrontab b/uscrontab index b8085b1..b681340 100755 --- a/uscrontab +++ b/uscrontab @@ -13,33 +13,118 @@ 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 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] +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. -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: +Les lignes commençant par # sont des commentaires et sont ignorées - 0 * * * * - 5 1 * * * command +== Définitions de variables et exécution de commandes == -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: + Les lignes de la forme suivante sont des définitions de variable: - PATH+=/usr/local/nutools - PATH-=/opt/rogue + [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, la ligne suivante permet d'exécuter 'command' + toutes les heures ET à 1h05: + + 0 * * * * + 5 1 * * * command + +== 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] + 0 1 * * * 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 + 0 1 * * * script1 + 0 1 * * * script2 + 0 1 * * * remove_pidfile /path/to/pid + 0 1 * * * 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 @@ -61,14 +146,14 @@ 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 $LOCKDELAY heures, un message d'erreur est loggé + 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[=$LOCKDELAY] + --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 @@ -88,15 +173,15 @@ source "$(dirname "$0")/ulib/ulib" && urequire DEFAULTS crontab || exit 1 -MY_CTLINE="* * * * * $script" -LOCKDELAY=8 +USCRONTAB_CTLINE="* * * * * $script" +USCRONTAB_LOCKDELAY=8 +USCRONTAB_STOPEC=101 action=run lockfile=auto lockdelay= fake= continuous= -stopec=101 parse_opts "${PRETTYOPTS[@]}" \ --help '$exit_with display_help' \ -A,--add,--install action=install \ @@ -105,7 +190,7 @@ parse_opts "${PRETTYOPTS[@]}" \ --lockdelay: lockdelay= \ -n,--fake fake=1 \ -c,--continuous continuous=1 \ - -k:,--stop: stopec=1 \ + -k:,--stop: USCRONTAB_STOPEC= \ -l,--list action=list \ @ args -- "$@" && set -- "${args[@]}" || die "$args" @@ -135,10 +220,10 @@ crontab="$1"; shift crontab="$(abspath "$crontab")" if [ "$action" == "install" ]; then - enable_in_crontab "$MY_CTLINE $(quoted_arg "$crontab")" && estep "add_to_crontab $MY_CTLINE $(quoted_arg "$crontab")" + enable_in_crontab "$USCRONTAB_CTLINE $(quoted_arg "$crontab")" && estep "add_to_crontab $USCRONTAB_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")" + 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 @@ -149,7 +234,7 @@ elif [ "$action" == "run" ]; then lockfile= fi fi - [ -n "$lockdelay" ] || lockdelay="$LOCKDELAY" + [ -n "$lockdelay" ] || lockdelay="$USCRONTAB_LOCKDELAY" if [ -n "$lockfile" ]; then lockwarn="${lockfile%.lock}.lockwarn" @@ -185,9 +270,15 @@ elif [ "$action" == "run" ]; then echo "$*" else edebug "$*" - (eval "$*"); ec=$? + 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 "$stopec" -a "$ec" == "$stopec" ] && exit 0 + [ -n "$USCRONTAB_STOPEC" -a "$ec" == "$USCRONTAB_STOPEC" ] && exit 0 [ -z "$continuous" -a "$ec" != 0 ] && exit "$ec" return 0 } @@ -197,7 +288,39 @@ elif [ "$action" == "run" ]; then ctscript="$(ctresolve <"$crontab")" ec=0 edebug "$ctscript" - (eval "$ctscript"); ec=$? + ( + 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 755 "$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