So protokollieren Sie Anrufe mithilfe eines Wrapper-Skripts, wenn mehrere Symlinks zur ausführbaren Datei vorhanden sind

8

Lange Rede, kurzer Sinn: Ich möchte nachverfolgen, wie einige ausführbare Dateien aufgerufen werden, um das Systemverhalten zu verfolgen. Angenommen, ich habe eine ausführbare Datei:

/usr/bin/do_stuff

Und es wird tatsächlich über Symlink unter verschiedenen Namen aufgerufen:

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

und so weiter. Es do_stuffist klar, dass das erste Argument, das es erhält, verwendet wird, um zu bestimmen, welche Aktion tatsächlich ausgeführt wird, und der Rest der Argumente wird vor diesem Hintergrund behandelt.

Ich würde gerne jemals einen Anruf aufzeichnen /usr/bin/do_stuff(und die vollständige Liste der Argumente). Wenn es keine Symlinks gäbe, würde ich einfach do_stuffzu do_stuff_realeinem Skript gehen und es schreiben

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

Da ich jedoch weiß, dass der Name, unter dem es aufgerufen wird, überprüft wird, funktioniert dies nicht. Wie schreibt man ein Skript, um dasselbe zu erreichen und trotzdem an do_stuffden richtigen 'ausführbaren benutzten Namen' weiterzugeben ?

Um Antworten in diesen Zeilen zu vermeiden:

  • Ich weiß, dass ich es in C machen kann (mit execve), aber es wäre viel einfacher, wenn ich in diesem Fall nur ein Shell-Skript verwenden könnte.
  • Ich kann nicht einfach durch do_stuffein Protokollierungsprogramm ersetzen .
Neil Townsend
quelle

Antworten:

6

Oft sieht man dies im Falle von Programmen wie busybox, ein Programm , das die meisten gängigen Unix - Dienstprogramme in einem ausführbaren zur Verfügung stellen kann, dass verschiedene benimmt sich auf ihrem Aufruf abhängig / busyboxkann eine ganze Reihe von Funktionen tun, acpiddurch zcat.

Und es entscheidet gewöhnlich, was es tun soll, indem es sich seinen argv[0]Parameter ansieht main(). Und das sollte kein einfacher Vergleich sein. Weil argv[0]es so etwas sein könnte sleepoder es sein könnte /bin/sleepund es sich entscheiden sollte, dasselbe zu tun. Mit anderen Worten, der Weg wird die Dinge komplexer machen.

Wenn also die Dinge vom Worker-Programm richtig gemacht wurden, könnte Ihr Protokollierungs-Wrapper von so etwas wie ausgeführt werden. /bin/realstuff/make_teaWenn der Worker argv[0]nur den Basisnamen betrachtet, sollte die richtige Funktion ausgeführt werden.

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

Im obigen Beispiel argv[0]sollte etwa so lauten /tmp/MYEXEC4321/make_tea(wenn 4321 die PID für die ausgeführte /bin/shwar), die das make_teaVerhalten des Basisnamens auslösen sollte

Wenn Sie argv[0]eine exakte Kopie dessen sein möchten, was ohne den Wrapper wäre, haben Sie ein schwierigeres Problem. Wegen absoluter Dateipfade beginnend mit /. Du kannst kein neues machen /bin/sleep (abwesend chrootund ich glaube nicht, dass du dorthin gehen willst). Wie Sie bemerken, könnten Sie das mit etwas Geschmack tun exec(), aber es wäre kein Shell-Wrapper.

Haben Sie darüber nachgedacht, einen Alias ​​zu verwenden, um den Logger zu treffen und dann das Basisprogramm anstelle eines Skript-Wrappers zu starten? Es würde nur eine begrenzte Anzahl von Ereignissen erfassen, aber vielleicht sind dies die einzigen Ereignisse, die Sie interessieren

angefügt
quelle
Musste den Basisnamen durch sed ersetzen, funktioniert aber sonst gut.
Neil Townsend
1
@NeilTownsend, mit einem POSIX shkönnen Sie mybase=${0##*/}anstelle des Basisnamens verwenden.
Stéphane Chazelas
@ StéphaneChazelas Ein basenameAufruf wie basename -- foo.barist mir unbekannt, und auf dem Linux-System, auf dem ich ihn getestet habe, wird das Ergebnis erzeugt, das foo.bardas Skript beschädigen würde. Sind Sie sicher, dass dies eine gängige Methode ist?
Infixed
basename -- foo.barkehrt zurück foo.bar, basename -- --foo--/barkehrt barwie erwartet zurück. basename "$foo"funktioniert nur, wenn Sie garantieren können, dass $fooes nicht mit einem beginnt -. Die allgemeine Syntax zum Aufrufen eines Befehls mit beliebigen Argumenten lautet cmd -x -y -- "$argument". cmd "$argument"ist falsch, es sei denn du meinst cmd "$option_or_arg". Einige Befehle (einschließlich einiger historischer Implementierungen von basename) werden jetzt nicht unterstützt, --sodass Sie manchmal zwischen Portabilität und Zuverlässigkeit wählen müssen.
Stéphane Chazelas
6

Sie können verwendet werden exec -a(wie gefunden in bash, ksh93, zsh, mksh, yashaber nicht POSIX noch) , die verwendet wird , ein , um anzugeben , argv[0]um einen Befehl wird ausgeführt:

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

Beachten Sie, dass dies nicht der Befehl $0ist , den argv[0]der Befehl empfängt. Es ist der Pfad des Skripts, an den übergeben wird execve()(und der als Argument übergeben wird bash), aber das reicht wahrscheinlich für Ihren Zweck aus.

Wenn beispielsweise Folgendes make_teaaufgerufen wurde:

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

Wie eine Shell normalerweise beim Aufrufen des Befehls nach Namen (Nachschlagen der ausführbaren Datei in $PATH) tun würde, würde der Wrapper Folgendes tun :

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

Das ist nicht:

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

aber das ist gut genug, da do_stuff_reales Tee machen soll.

Wo das ein Problem wäre, wäre, wenn do_stuffaufgerufen würde als:

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

wie das übersetzt werden würde zu:

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

Das würde im normalen Betrieb nicht passieren, aber beachten Sie, dass unser Wrapper so etwas tut.

Auf den meisten Systemen geht das argv[0]an das Skript übergebene Element verloren, sobald der Interpreter (hier /bin/bash) ausgeführt wird ( der argv[0]Interpreter auf den meisten Systemen ist der in der She-Bang-Zeile angegebene Pfad ), sodass ein Shell-Skript nichts dagegen tun kann.

Wenn Sie das weitergeben argv[0]möchten, müssen Sie eine ausführbare Datei kompilieren. Etwas wie:

#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
quelle
+1: Dies wäre die richtige Antwort, außer dass die Shell auf dem System, an dem ich arbeiten möchte, nicht die Option -a für exec bietet ...
Neil Townsend
@NeilTownsend Sie könnten etwas Ähnliches mit perloder pythonfalls verfügbar machen. Ältere Versionen von zshwurden nicht unterstützt exec -a, können aber ARGV0=the-argv-0 cmd argsstattdessen immer verwendet werden.
Stéphane Chazelas
Es ist eine Busybox-basierte Maschine, daher scheint die Shell Asche zu sein, die nicht die Flagge -a zu haben scheint. Oder zumindest bei dieser Version. Da ich gerade an der Firmware arbeite und sie nicht klar genug im Griff habe, um etwas Anmaßendes zu tun, versuche ich zu vermeiden, zu viel hinzuzufügen, nur um zu verstehen, wie sie startet. Ich denke, ARGV0 ist eine zsh einzige Sache?
Neil Townsend
@NeilTownsend, ja, das ist eine reine Zsh-Sache. Ich meinte, wenn Sie es getan hätten zsh(obwohl Sie jetzt klar gemacht haben, dass Sie es nicht getan haben), aber es zu alt war, könnten Sie es trotzdem verwenden ARGV0.
Stéphane Chazelas
Fair genug - Wenn ich Ihre Antwort als "brillant" ankreuzen könnte, aber für meine dunkle Situation nicht wirklich funktioniert ", würde ich.
Neil Townsend