diff --git a/lib/uinst/rootconf b/lib/uinst/rootconf index c83f89f..c3cbb6f 100644 --- a/lib/uinst/rootconf +++ b/lib/uinst/rootconf @@ -71,3 +71,13 @@ etitle "Installation de /etc/init.d/kvm-stop-all" \ [ -n "$openvz_service" ] && etitle "Installation de /etc/init.d/openvz-fix-etchosts" \ "$scriptdir/../init.d/install-openvz-fix-etchosts" + +etitle "Installation des répertoires pour uscrontab" +if mkdir -p /var/uscrontab/crontabs; then + chmod 1733 /var/uscrontab/crontabs +else + eerror "Impossible de créer le répertoire /var/uscrontab/crontabs" +fi +eend + +exit 0 diff --git a/ulib/.ulib_version b/ulib/.ulib_version index a787364..8f92bfd 100644 --- a/ulib/.ulib_version +++ b/ulib/.ulib_version @@ -1 +1 @@ -34 +35 diff --git a/ulib/base b/ulib/base index 30a4ebc..3490d85 100644 --- a/ulib/base +++ b/ulib/base @@ -38,9 +38,9 @@ function recho() { if [[ "${1:0:2}" == -[eEn] ]]; then echo -n - local first="${1:1}"; shift - echo "$first" "$@" + echo "$first$@" else - echo "$*" + echo "$@" fi } function recho_() { @@ -49,9 +49,9 @@ function recho_() { if [[ "${1:0:2}" == -[eEn] ]]; then echo -n - local first="${1:1}"; shift - echo -n "$first" "$@" + echo -n "$first$@" else - echo -n "$*" + echo -n "$@" fi } function qval() { @@ -88,7 +88,7 @@ function qvals() { [ -z "$first" ] && echo -n " " if should_quote "$arg"; then echo -n \" - qv "$arg" + qval "$arg" echo -n \" else recho_ "$arg" @@ -733,7 +733,7 @@ function array_extend_lasts() { function array_xsplit() { # créer le tableau $1 avec chaque élément de $2 (un ensemble d'éléments séparés # par $3, qui vaut ':' par défaut). - eval "$1=($(<<<"$2" stripnl | awkrun RS="${3:-:}" ' + eval "$1=($(recho_ "$2" | awkrun RS="${3:-:}" ' { gsub(/'\''/, "'\'\\\\\'\''") print "'\''" $0 "'\''" @@ -743,7 +743,7 @@ function array_split() { # créer le tableau $1 avec chaque élément de $2 (un ensemble d'éléments séparés # par $3, qui vaut ':' par défaut). Les éléments vides sont ignorés. par exemple # "a::b" est équivalent à "a:b" - eval "$1=($(<<<"$2" stripnl | awkrun RS="${3:-:}" ' + eval "$1=($(recho_ "$2" | awkrun RS="${3:-:}" ' /^$/ { next } { gsub(/'\''/, "'\'\\\\\'\''") @@ -755,7 +755,7 @@ function array_from_path() { } function array_from_xlines() { # créer le tableau $1 avec chaque ligne de $2. - eval "$1=($(<<<"$2" _nl2lf | awk ' + eval "$1=($(recho_ "$2" | _nl2lf | awk ' { gsub(/'\''/, "'\'\\\\\'\''") print "'\''" $0 "'\''" @@ -763,7 +763,7 @@ function array_from_xlines() { } function array_from_lines() { # créer le tableau $1 avec chaque ligne de $2. Les lignes vides sont ignorés. - eval "$1=($(<<<"$2" _nl2lf | awk ' + eval "$1=($(recho_ "$2" | _nl2lf | awk ' /^$/ { next } { gsub(/'\''/, "'\'\\\\\'\''") diff --git a/uscrontab b/uscrontab index 782f1b8..7b71a3e 100755 --- a/uscrontab +++ b/uscrontab @@ -6,13 +6,22 @@ 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] + $scriptname [options] [/path/to/crontab] + $scriptname -e [/path/to/crontab] + $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. +Si aucun fichier n'est spécifié, fusionner s'il existe le fichier + $USCRONTAB_USERFILE +avec chacun des fichiers du répertoire + $USCRONTAB_USERDIR +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 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. @@ -157,17 +166,29 @@ Les lignes commençant par # sont des commentaires et sont ignorées 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. + Installer une planification toutes les minutes du script dans la crontab + de l'utilisateur. Si l'argument /path/to/crontab n'est pas spécifié, + c'est une planification générique qui exécute les fichiers par défaut. -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. + Désinstaller la planification toutes les minutes du script du crontab de + l'utilisateur. Si l'argument /path/to/crontab est spécifié, cette + instance est désinstallée. Sinon, ne désinstaller que la planification + générique. + -e, --edit + Lancer un editeur pour modifier la crontab spécifiée. Si aucun fichier + n'est spécifié, éditer $USCRONTAB_USERFILE + -r, --remove + Supprimer le fichier $USCRONTAB_USERFILE s'il existe + Si l'argument /path/to/crontab est spécifié, il est ignoré. -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 son exécution a été planifiée. + 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 @@ -198,9 +219,23 @@ OPTIONS AVANCEES le traitement." } +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[@]}")' +} + USCRONTAB_CTLINE="* * * * * $script" USCRONTAB_LOCKDELAY=8 USCRONTAB_STOPEC=101 +USCRONTAB_BASEDIR=/var/uscrontab +USCRONTAB_USERFILE="$USCRONTAB_BASEDIR/crontabs/$USER" +USCRONTAB_USERDIR="$USCRONTAB_BASEDIR/$USER.d" +USCRONTAB_USER="$USCRONTAB_BASEDIR/$USER" action=run lockfile=auto @@ -217,43 +252,97 @@ parse_opts "${PRETTYOPTS[@]}" \ -c,--continuous continuous=1 \ -k:,--stop: USCRONTAB_STOPEC= \ -l,--list action=list \ + -e,--edit action=edit \ + -r,--remove action=remove \ @ args -- "$@" && set -- "${args[@]}" || die "$args" -if [ "$action" == "list" ]; then - crontab="$1"; shift - [ -n "$crontab" ] && crontab="$(abspath "$crontab")" +crontab="$1"; shift - 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é" +if [ "$action" == "edit" ]; then + if [ -z "$crontab" ]; then + basedir="$(dirname "$USCRONTAB_USERFILE")" + [ -d "$basedir" ] || die "$basedir: ce répertoire n'existe pas. Vérifiez l'installation de nutools" + crontab="$USCRONTAB_USERFILE" + fi + enote "Edition de $crontab" + if [ ! -f "$crontab" ]; then + touch "$crontab" + chmod 640 "$crontab" + fi + "${EDITOR:-vi}" "$crontab" + exit 0 + +elif [ "$action" == "remove" ]; then + [ -n "$crontab" ] && ewarn "$crontab: cet argument a été ignoré" + crontab="$USCRONTAB_USERFILE" + if [ -f "$crontab" ]; then + ask_yesno "Voulez-vous supprimer le fichier $crontab?" C || die + enote "Suppression de $crontab" + rm "$crontab" || die fi exit 0 + +elif [ "$action" == "list" ]; then + if [ -n "$crontab" ]; then + crontab="$(abspath "$crontab")" + array_from_lines ctfiles "$(crontab -l 2>/dev/null | awkrun script="$script" crontab="$crontab" '$6 == script && $7 == crontab { print $7 }')" + if [ ${#ctfiles[*]} -eq 0 ]; then + ewarn "$(ppath "$crontab"): non planifié" + ctfiles=("$crontab") + fi + else + array_from_lines ctfiles "$(crontab -l 2>/dev/null | awkrun script="$script" '$6 == script { if ($7) print $7; 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 [ ${#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 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")" +[ -z "$crontab" -o -f "$crontab" ] || die "$crontab: fichier introuvable" +[ -n "$crontab" ] && crontab="$(abspath "$crontab")" if [ "$action" == "install" ]; then - enable_in_crontab "$USCRONTAB_CTLINE $(quoted_arg "$crontab")" && estep "add_to_crontab $USCRONTAB_CTLINE $(quoted_arg "$crontab")" + ctline="$USCRONTAB_CTLINE" + [ -n "$crontab" ] && ctline="$ctline $(quoted_arg "$crontab")" + enable_in_crontab "$ctline" && estep "add_to_crontab $ctline" elif [ "$action" == "uninstall" ]; then - remove_from_crontab "$USCRONTAB_CTLINE $(quoted_arg "$crontab")" && estep "remove_from_crontab $USCRONTAB_CTLINE $(quoted_arg "$crontab")" + ctline="$USCRONTAB_CTLINE" + [ -n "$crontab" ] && ctline="$ctline $(quoted_arg "$crontab")" + remove_from_crontab "$ctline" && estep "remove_from_crontab $ctline" elif [ "$action" == "run" ]; then + clean_crontab= + if [ -n "$crontab" ]; then + default_lockfile="/var/run/$scriptname$crontab.lock" + else + set_usercrontabs usercrontabs + ac_set_tmpfile crontab + clean_crontab=1 + for usercrontab in "${usercrontabs[@]}"; do + echo "# $usercrontab" >>"$crontab" + cat "$usercrontab" >>"$crontab" + done + default_lockfile="/var/run/$scriptname$USCRONTAB_USER.lock" + fi + if [ "$lockfile" == auto ]; then if is_root; then - lockfile="/var/run/$scriptname$crontab.lock" + lockfile="$default_lockfile" mkdirof "$lockfile" || die else lockfile= @@ -263,6 +352,8 @@ elif [ "$action" == "run" ]; then if [ -n "$lockfile" ]; then lockwarn="${lockfile%.lock}.lockwarn" + autoclean "$lockwarn" + retry=1 while [ -n "$retry" ]; do case "$(lf_trylock -h "$lockdelay" "$lockfile")" in @@ -285,7 +376,8 @@ elif [ "$action" == "run" ]; then *) retry=;; esac done - [ -f "$lockwarn" ] && rm "$lockwarn" + + ac_clean "$lockwarn" autoclean "$lockfile" fi @@ -314,21 +406,29 @@ elif [ "$action" == "run" ]; then 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 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 + 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 @@ -340,12 +440,16 @@ puis supprimez le cas échéant le fichier $1" return 0 } function remove_pidfile() { - [ -n "$1" ] && ac_clean "$1" + if [ -n "$1" ]; then + local pidfile="$(abspath "$1")" + ac_clean "$pidfile" + array_del __USCRONTAB_PIDFILES "$pidfile" + fi } __ac_forgetall eval "$ctscript" ac_cleanall ); ec=$? - [ -f "$lockfile" ] && rm "$lockfile" + ac_clean "$lockfile" exit "$ec" fi