Comment consigner les appels à l'aide d'un script wrapper lorsqu'il existe plusieurs liens symboliques vers l'exécutable

8

Pour faire court: je voudrais suivre la façon dont certains exécutables sont appelés pour suivre certains comportements du système. Disons que j'ai un exécutable:

/usr/bin/do_stuff

Et il est en fait appelé par un certain nombre de noms différents via un lien symbolique:

/usr/bin/make_tea -> /usr/bin/do_stuff
/usr/bin/make_coffee -> /usr/bin/do_stuff

etc. De toute évidence, do_stuffva utiliser le premier argument qu'il reçoit pour déterminer quelle action est réellement prise, et le reste des arguments sera traité à la lumière de cela.

Je voudrais enregistrer chaque appel à /usr/bin/do_stuff(et la liste complète des arguments). S'il n'y avait pas de liens symboliques, je voudrais simplement passer do_stuffà do_stuff_realet d' écrire un script

#!/bin/sh
echo "$0 $@" >> logfile
/usr/bin/do_stuff_real "$@"

Cependant, comme je sais qu'il examinera le nom par lequel il est appelé, cela ne fonctionnera pas. Comment peut-on écrire un script pour atteindre le même résultat tout en transmettant le do_stuffbon «nom utilisé exécutable»?

Pour mémoire, pour éviter les réponses sur ces lignes:

  • Je sais que je peux le faire en C (en utilisant execve), mais ce serait beaucoup plus facile si je pouvais, dans ce cas, simplement utiliser un script shell.
  • Je ne peux pas simplement remplacer do_stuffpar un programme de journalisation.
Neil Townsend
la source

Réponses:

6

Vous voyez souvent cela dans le cas d'utilitaires comme busybox, un programme qui peut fournir la plupart des utilitaires Unix communs dans un exécutable, qui se comporte différemment selon son invocation / busyboxpeut faire beaucoup de fonctions, acpidvia zcat.

Et il décide généralement ce qu'il est censé faire en regardant son argv[0]paramètre main(). Et cela ne devrait pas être une simple comparaison. Parce argv[0]que ça pourrait être quelque chose comme ça sleep, ou ça pourrait être /bin/sleepet ça devrait décider de faire la même chose. En d'autres termes, le chemin va rendre les choses plus complexes.

Donc, si les choses ont été faites correctement par le programme de travail, votre wrapper de journalisation pourrait s'exécuter à partir de quelque chose comme /bin/realstuff/make_teaet si le travailleur ne regarde argv[0]que le nom de base, alors la bonne fonction devrait s'exécuter.

#!/bin/sh -
myexec=/tmp/MYEXEC$$
mybase=`basename -- "$0"`

echo "$0 $@" >> logfile

mkdir "$myexec" || exit
ln -fs /usr/bin/real/do_stuff "$myexec/$mybase" || exit
"$myexec/$mybase" "$@"
ret=$?
rm -rf "$myexec"
exit "$ret"

Dans l'exemple ci-dessus, argv[0]devrait lire quelque chose comme /tmp/MYEXEC4321/make_tea(si 4321 était le PID pour celui /bin/shqui s'est exécuté) qui devrait déclencher le make_teacomportement du nom de base

Si vous voulez argv[0]être une copie exacte de ce que ce serait sans le wrapper, vous avez un problème plus difficile. En raison des chemins de fichiers absolus commençant par /. Vous ne pouvez pas en faire un nouveau /bin/sleep (absent chrootet je ne pense pas que vous vouliez y aller). Comme vous le constatez, vous pourriez le faire avec une certaine saveur de exec(), mais ce ne serait pas un wrapper shell.

Avez-vous envisagé d'utiliser un alias pour frapper l'enregistreur, puis démarrer le programme de base au lieu d'un wrapper de script? Cela n'attraperait qu'un ensemble limité d'événements, mais ce sont peut-être les seuls événements qui vous intéressent

infixé
la source
J'ai dû remplacer le nom de base par sed, mais sinon ça marche bien.
Neil Townsend
1
@NeilTownsend, avec un POSIX sh, vous pouvez utiliser à la mybase=${0##*/}place du nom de base.
Stéphane Chazelas
@ StéphaneChazelas Une basenameinvocation comme ça basename -- foo.barne m'est pas familière, et sur le système Linux sur foo.barlequel je l'ai testée produit le résultat qui casserait le script. Êtes-vous sûr que c'est une méthode courante?
infixé le
basename -- foo.barrenvoie foo.bar, basename -- --foo--/barretourne barcomme prévu. basename "$foo"ne fonctionne que si vous pouvez garantir $foone commence pas par un -. La syntaxe générale pour appeler une commande avec des arguments arbitraires est cmd -x -y -- "$argument". cmd "$argument"est faux, sauf si vous voulez dire cmd "$option_or_arg". Maintenant, certaines commandes (y compris certaines implémentations historiques de basename) ne prennent pas en charge --, vous devrez donc parfois choisir entre la portabilité et la fiabilité.
Stéphane Chazelas
6

Vous pouvez utiliser exec -a(que l'on trouve dans bash, ksh93, zsh, mksh, yashmais pas encore Posix) qui est utilisé pour spécifier une argv[0]à une commande en cours d' exécution:

#! /bin/bash -
printf '%s\n' "$0 $*" >> /some/log
exec -a "$0" /usr/bin/do_stuff_real "$@"

Notez que ce $0n'est pas ce argv[0]que la commande reçoit. C'est le chemin du script tel que transmis à execve()(et qui est transmis comme argument bash), mais cela est probablement suffisant pour votre objectif.

Par exemple, si a make_teaété invoqué comme:

execv("/usr/bin/make_tea", ["make_tea", "--sugar=2"])

Comme le ferait généralement un shell lors de l'appel de la commande par son nom (en recherchant l'exécutable dans $PATH), l'encapsuleur devrait:

execv("/usr/bin/do_stuff_real", ["/usr/bin/make_tea", "--sugar=2"])

Ce n'est pas:

execv("/usr/bin/do_stuff_real", ["make_tea", "--sugar=2"])

mais c'est assez bon car on do_stuff_realsait que c'est fait pour faire du thé.

Là où ce serait un problème, c'est si a do_stuffété invoqué comme:

execv("/usr/bin/do_stuff", ["/usr/bin/make_tea", "--sugar=2"])

car cela se traduirait par:

execv("/usr/bin/do_stuff_real", ["/usr/bin/do_stuff", "--sugar=2"])

Cela ne se produirait pas pendant les opérations normales, mais notez que notre wrapper fait quelque chose comme ça.

Sur la plupart des systèmes, le argv[0]tel que transmis au script est perdu une fois que l'interpréteur (ici /bin/bash) est exécuté ( le argv[0]de l'interpréteur sur la plupart des systèmes est le chemin indiqué sur la ligne de coup ) donc il n'y a rien qu'un script shell puisse faire à ce sujet.

Si vous vouliez passer le argv[0]long, vous auriez besoin de compiler un exécutable. Quelque chose comme:

#include <stdio.h>
int main(int argc, char *argv[], char *envp[])
{
   /* add logging */
   execve("/usr/bin/do_stuff_real", argv, envp);
   perror("execve");
   return 127;
}
Stéphane Chazelas
la source
+1: Ce serait la bonne réponse, sauf que le shell sur le système sur lequel j'essaie de travailler ne fournit pas l'option -a pour l'exec ...
Neil Townsend
@NeilTownsend Vous pouvez faire quelque chose de similaire avec perlou pythonsi disponible. Les anciennes versions de zshne prenaient pas en charge exec -a, mais vous pouvez toujours utiliser à la ARGV0=the-argv-0 cmd argsplace.
Stéphane Chazelas
C'est une machine basée sur busybox, donc le shell semble être de la cendre, qui ne semble pas avoir le drapeau -a. Ou, au moins, sur cette version. Comme je suis en train de travailler sur le firmware, et que je n'ai pas une prise en main suffisamment claire pour faire quoi que ce soit de présomptueux, j'essaie d'éviter d'en ajouter trop juste pour comprendre comment il démarre. Je pense que ARGV0 est une seule chose zsh?
Neil Townsend
@NeilTownsend, oui, c'est une chose uniquement zsh. Je voulais dire que si vous l'aviez fait zsh(bien que maintenant vous ayez clairement indiqué que vous ne l'avez pas fait) mais qu'il était trop ancien, vous pourriez toujours l'utiliser ARGV0.
Stéphane Chazelas
Assez juste - Si je pouvais cocher votre réponse pour avoir été «brillante mais qui n'a pas vraiment fonctionné pour ma situation obscure», je le ferais.
Neil Townsend