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

8

Para encurtar a história: eu gostaria de acompanhar o modo como alguns executáveis ​​são chamados para rastrear algum comportamento do sistema. Digamos que eu tenho um executável:

/usr/bin/do_stuff

E na verdade é chamado por vários nomes diferentes via link simbólico:

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

e assim por diante. Claramente, do_stuffusaremos o primeiro argumento recebido para determinar qual ação é realmente executada, e o restante dos argumentos será tratado à luz disso.

Gostaria de gravar sempre chamadas para /usr/bin/do_stuff(e a lista completa de argumentos). Se não houvesse links simbólicos, eu simplesmente mudaria do_stuffpara do_stuff_reale escreveria um script

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

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

Para o registro, para evitar respostas nessas linhas:

  • Eu sei que posso fazê-lo em C (usando execve), mas seria muito mais fácil se eu pudesse, nesse caso, usar apenas um script de shell.
  • Não posso simplesmente substituir do_stuffpor um programa de registro.
Neil Townsend
fonte

Respostas:

6

Muitas vezes você vê isso em caso de utilitários como busybox, um programa que pode fornecer a maioria dos utilitários comuns do Unix em um executável, que se comporta diferentes, dependendo de sua invocação / busyboxpode fazer um monte de funções, acpidatravés zcat.

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

Portanto, se as coisas foram feitas corretamente pelo programa worker, seu wrapper de log pode ser executado a partir de algo como /bin/realstuff/make_teae se o trabalhador olhar argv[0]apenas o nome da base, 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 foi o PID para o /bin/shque foi executado) que deve disparar o make_teacomportamento do nome da base

Se você deseja argv[0]ser uma cópia exata do que seria sem o invólucro, você tem um problema mais difícil. Devido aos caminhos de arquivo absolutos que começam com /. Você não pode fazer um novo /bin/sleep (ausente chroote eu não acho que você queira ir lá). Como você observa, você poderia fazer isso com algum sabor exec(), mas não seria um invólucro.

Você já pensou em usar um alias para acessar o criador de logs e iniciar o programa base em vez de um wrapper de script? Ele capturaria apenas um conjunto limitado de eventos, mas talvez esses sejam os únicos eventos com os quais você se preocupa

infixado
fonte
Teve que substituir basename com sed, mas de outra forma funciona bem.
Neil Townsend
1
@NeilTownsend, com um POSIX sh, você pode usar em mybase=${0##*/}vez de basename.
Stéphane Chazelas
@ StéphaneChazelas Uma basenameinvocação como essa basename -- foo.barnão é familiar para mim, e no sistema Linux em que a testei, produz o resultado foo.barque quebraria o script. Tem certeza de que é um método comum?
infixado em 7/06/16
basename -- foo.barretorna foo.bar, basename -- --foo--/barretorna barcomo esperado. basename "$foo"só funciona se você pode garantir $fooque não comece com a -. A sintaxe geral para chamar um comando com argumentos arbitrários é cmd -x -y -- "$argument". cmd "$argument"está errado, a menos que você queira dizer cmd "$option_or_arg". Agora, alguns comandos (incluindo algumas implementações históricas de basename) não são compatíveis, --portanto, às vezes, você pode escolher entre portabilidade e confiabilidade.
Stéphane Chazelas
6

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

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

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

Como exemplo, se make_teafoi chamado 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 deveria:

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, como se do_stuff_realsabe, para fazer chá.

Onde isso seria um problema é se do_stufffoi invocado como:

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

pois isso seria traduzido para:

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

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

Na maioria dos sistemas, o argv[0]conforme passado para o script é perdido depois que o intérprete (aqui /bin/bash) é executado ( o argv[0]intérprete na maioria dos sistemas é o caminho fornecido na linha she-bang ), portanto não há nada que um script de shell possa fazer a respeito.

Se você quisesse transmitir argv[0]adiante, seria necessário 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;
}
Stéphane Chazelas
fonte
+1: Esta seria a resposta certa, exceto que o shell no sistema que estou tentando trabalhar em não fornece a opção -a para exec ...
Neil Townsend
@NeilTownsend Você pode fazer algo semelhante com perlou pythonse disponível. As versões mais antigas do zshnão eram compatíveis exec -a, mas você sempre pode usá-lo ARGV0=the-argv-0 cmd args.
Stéphane Chazelas
É uma máquina baseada em busybox, então o shell parece ser cinza, o que não parece ter a bandeira -a. Ou, pelo menos, nesta versão. Como estou trabalhando no firmware, e não tenho um controle claro o suficiente para fazer algo presunçoso, estou tentando evitar adicionar muito a ele apenas para entender como ele é iniciado. Eu acho que o ARGV0 é uma coisa única do zsh?
Neil Townsend
@ NeilTownsend, sim, isso é apenas uma coisa do zsh. Eu quis dizer que se você tivesse zsh(embora agora você tenha deixado claro que não), mas era muito antigo, você ainda poderia usá ARGV0-lo.
Stéphane Chazelas
Suficiente Fair - Se eu pudesse assinalar a sua resposta por ser 'brilhante, mas na verdade não trabalho para a minha situação obscura" eu o faria.
Neil Townsend