Come registrare le chiamate utilizzando uno script wrapper in presenza di più collegamenti simbolici all'eseguibile


8

Per farla breve: vorrei tracciare il modo in cui vengono chiamati alcuni eseguibili per tenere traccia del comportamento del sistema. Diciamo che ho un eseguibile:

/usr/bin/do_stuff

Ed è in realtà chiamato da un numero di nomi diversi tramite symlink:

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

e così via. Chiaramente, do_stuffutilizzerà il primo argomento ricevuto per determinare quale azione viene effettivamente intrapresa, e il resto degli argomenti verrà gestito alla luce di ciò.

Vorrei registrare mai chiamare /usr/bin/do_stuff(e l'elenco completo degli argomenti). Se non ci fossero collegamenti simbolici, vorrei semplicemente muovo do_stuffper do_stuff_reale scrivere uno script

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

Tuttavia, poiché so che esaminerà il nome da cui viene chiamato, questo non funzionerà. Come si può scrivere uno script per ottenere lo stesso, ma passare comunque al do_stuffgiusto 'nome usato eseguibile'?

Per la cronaca, per evitare risposte su queste righe:

  • So che posso farlo in C (usando execve), ma sarebbe molto più semplice se potessi, in questo caso, usare solo uno script di shell.
  • Non posso semplicemente sostituire do_stuffcon un programma di registrazione.

Risposte:


6

Si vede spesso questo nel caso di programmi di utilità come busybox, un programma che può fornire la maggior parte dei programmi di utilità unix comuni in un eseguibile, che si comporta in modo diverso a seconda della sua chiamata / che busyboxpuò svolgere molte funzioni, acpidattraverso zcat.

E comunemente decide cosa dovrebbe fare guardando il suo argv[0]parametro main(). E questo non dovrebbe essere un semplice confronto. Perché argv[0]potrebbe essere qualcosa del genere sleep, o potrebbe essere /bin/sleepe dovrebbe decidere di fare la stessa cosa. In altre parole, il percorso renderà le cose più complesse.

Quindi, se le cose sono state eseguite correttamente dal programma di lavoro, il wrapper di registrazione potrebbe essere eseguito da qualcosa del genere /bin/realstuff/make_teae se il lavoratore guarda argv[0]solo il nome di base, allora la funzione giusta dovrebbe essere eseguita.

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

Nell'esempio sopra, argv[0]dovrebbe leggere qualcosa del tipo /tmp/MYEXEC4321/make_tea(se 4321 era il PID per quello /bin/shche correva) che dovrebbe innescare il make_teacomportamento basename

Se vuoi argv[0]essere una copia esatta di ciò che sarebbe senza il wrapper, hai un problema più difficile. A causa dei percorsi di file assoluti che iniziano con /. Non puoi crearne uno nuovo /bin/sleep (assente chroote non credo che tu voglia andarci). Come noti, potresti farlo con un po 'di sapore exec(), ma non sarebbe un involucro di shell.

Hai mai pensato di utilizzare un alias per colpire il logger e quindi avviare il programma di base anziché un wrapper di script? Catturerebbe solo un numero limitato di eventi, ma forse quelli sono gli unici eventi a cui tieni


Ho dovuto sostituire basename con sed, ma per il resto funziona bene.
Neil Townsend,

1
@NeilTownsend, con POSIX sh, puoi usare al mybase=${0##*/}posto di basename.
Stéphane Chazelas,

@ StéphaneChazelas basenameUn'invocazione simile basename -- foo.barnon mi è familiare, e sul sistema Linux su cui l'ho provato produce il risultato foo.barche spezzerebbe lo script. Sei sicuro che sia un metodo comune?
apposto il

basename -- foo.barritorna foo.bar, basename -- --foo--/barritorna barcome previsto. basename "$foo"funziona solo se puoi garantire $fooche non inizi con a -. La sintassi generale per invocare un comando con argomenti arbitrari è cmd -x -y -- "$argument". cmd "$argument"è sbagliato a meno che tu non intenda cmd "$option_or_arg". Ora, alcuni comandi (incluse alcune implementazioni storiche di basename) non supportano, --quindi a volte potresti dover scegliere tra portabilità e affidabilità.
Stéphane Chazelas,

6

È possibile utilizzare exec -a(come si trova in bash, ksh93, zsh, mksh, yashma non POSIX ancora) che viene utilizzato per specificare un argv[0]ad un comando viene eseguito:

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

Si noti che non$0 è quello che riceve il comando. È il percorso dello script come passato (e che viene passato come argomento a ), ma è probabile che sia sufficiente per il tuo scopo.argv[0]execve()bash

Ad esempio, se è make_teastato invocato come:

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

Come farebbe normalmente una shell quando si richiama il comando per nome (cercando il file eseguibile $PATH), il wrapper dovrebbe:

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

Questo non è:

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

ma è abbastanza buono come do_stuff_realsa che è pensato per fare il tè.

Dove questo sarebbe un problema è se è do_stuffstato invocato come:

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

come sarebbe tradotto in:

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

Ciò non accadrebbe durante le normali operazioni, ma nota che il nostro wrapper fa qualcosa del genere.

Sulla maggior parte dei sistemi, l' argv[0]as passato allo script viene perso una volta /bin/basheseguito l'interprete (qui ) ( quello argv[0]dell'interprete sulla maggior parte dei sistemi è il percorso indicato sulla linea she-bang ), quindi non c'è nulla che uno script di shell possa fare al riguardo.

Se si desidera passare il argv[0]lungo, è necessario compilare un eseguibile. Qualcosa di simile a:

#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;
}

+1: Questa sarebbe la risposta giusta, tranne per il fatto che la shell sul sistema su cui sto cercando di lavorare non fornisce l'opzione -a per exec ...
Neil Townsend

@NeilTownsend Puoi fare qualcosa di simile con perlo pythonse disponibile. Le versioni precedenti di zshnon supportavano exec -a, ma puoi sempre utilizzare ARGV0=the-argv-0 cmd argsinvece.
Stéphane Chazelas,

È una macchina basata su busybox, quindi la shell sembra essere cenere, che non sembra avere la bandiera -a. O, almeno, in questa versione. Dato che sto lavorando al firmware e non ho una presa abbastanza chiara per fare qualcosa di presuntuoso, sto cercando di evitare di aggiungere troppo ad esso solo per capire come si avvia. Penso che ARGV0 sia una sola cosa zsh?
Neil Townsend,

@NeilTownsend, sì, è una cosa solo per zsh. Volevo dire che se lo avessi fatto zsh(anche se ora hai chiarito che non lo hai fatto) ma era troppo vecchio, potresti ancora usarlo ARGV0.
Stéphane Chazelas,

Abbastanza giusto - Se potessi spuntare la tua risposta per essere "geniale ma in realtà non ha funzionato per la mia oscura situazione", lo farei.
Neil Townsend,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.