Como logar chamadas usando um script wrapper quando existem vários links simbólicos para o executável

8

Longa história: gostaria de rastrear a maneira como alguns executáveis são chamados para rastrear algum comportamento do sistema. Digamos que eu tenha um executável:

/usr/bin/do_stuff

E na verdade ele é chamado por vários nomes diferentes por meio de symlink:

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

e assim por diante. Claramente, do_stuff vai usar o primeiro argumento que recebe para determinar qual ação é realmente tomada, e o restante dos argumentos serão tratados à luz disso.

Eu gostaria de gravar sempre uma chamada para /usr/bin/do_stuff (e a lista completa de argumentos). Se não houvesse links simbólicos, eu simplesmente moveria do_stuff para do_stuff_real e escreveria um script

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

No entanto, como sei que ele examinará o nome pelo qual é chamado, isso não funcionará. Como alguém escreve um script para conseguir o mesmo, mas ainda passa para do_stuff o nome 'executável usado' correto?

Para o registro, para evitar respostas nestas linhas:

  • Eu sei que posso fazer isso em C (usando execve), mas seria muito mais fácil se eu pudesse, neste caso, apenas usar um script de shell.
  • Não posso simplesmente substituir do_stuff por um programa de registro.
por Neil Townsend 07.06.2016 / 18:04

2 respostas

6

Você geralmente vê isso no caso de utilitários como busybox , um programa que pode fornecer a maioria dos utilitários unix comuns em um executável, que se comporta diferente dependendo de sua invocação / busybox pode executar várias funções, acpid a zcat .

E geralmente decide o que deve fazer olhando o parâmetro argv[0] para main() . E isso não deveria ser uma comparação simples. Porque argv[0] pode ser algo como sleep ou pode ser /bin/sleep e deve decidir fazer a mesma coisa. Em outras palavras, o caminho vai tornar as coisas mais complexas.

Portanto, se as coisas forem feitas corretamente pelo programa worker, seu wrapper de logging poderá ser executado a partir de algo como /bin/realstuff/make_tea e se o worker examinar argv[0] basename only, a função correta deverá ser executada.

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

No exemplo acima, argv[0] deve ler algo como /tmp/MYEXEC4321/make_tea (se 4321 fosse o PID para o /bin/sh que foi executado), o que deve acionar o basename make_tea behavior

Se você quiser que argv[0] seja uma cópia exata do que seria sem o wrapper, você terá um problema mais difícil. Por causa dos caminhos de arquivos absolutos que começam com / . Você não pode criar um novo /bin/sleep (ausente chroot e eu não acho que você queira ir lá). Como você observou, você poderia fazer isso com algum sabor de exec() , mas não seria um invólucro de shell.

Já pensou em usar um alias para acertar o registrador e iniciar o programa de base em vez de um wrapper de script? Ele só pegaria um conjunto limitado de eventos, mas talvez esses sejam os únicos eventos com os quais você se importa

    
por 07.06.2016 / 18:53
6

Você pode usar exec -a (conforme encontrado em bash , ksh93 , zsh , mksh , yash mas ainda não POSIX) usado para especificar um argv[0] para um comando que está sendo executado:

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

Observe que $0 não é o argv[0] que o comando recebe. É o caminho do script que foi passado para execve() (e que é passado como argumento para bash ), mas é provável que seja suficiente para o seu propósito.

Como exemplo, se make_tea foi invocado como:

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

Como um shell normalmente faria ao chamar o comando pelo nome (procurando o executável em $PATH ), o wrapper usaria:

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

Isso não é:

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

mas isso é bom o suficiente, pois do_stuff_real sabe que é feito para fazer chá.

Em que isso seria um problema se do_stuff fosse invocado como:

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

como isso seria traduzido para:

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

Isso não aconteceria durante as operações normais, mas observe que nosso wrapper faz algo assim.

Na maioria dos sistemas, o argv[0] como passado para o script é perdido quando o intérprete (aqui /bin/bash ) é executado ( o argv[0] do interpretador na maioria dos sistemas é o caminho dado na linha she-bang , então não há nada que um shell script possa fazer sobre isso.

Se você quisesse passar o argv[0] , precisaria compilar um executável. Algo como:

#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;
}
    
por 07.06.2016 / 18:31