Cara mencatat panggilan menggunakan skrip wrapper ketika ada beberapa symlink ke executable

8

Singkat cerita: Saya ingin melacak cara beberapa executable dipanggil untuk melacak beberapa perilaku sistem. Katakanlah saya memiliki executable:

/usr/bin/do_stuff

Dan itu sebenarnya dipanggil oleh sejumlah nama yang berbeda melalui symlink:

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

dan seterusnya. Jelas, do_stuffakan menggunakan argumen pertama yang diterimanya untuk menentukan tindakan apa yang sebenarnya diambil, dan sisa argumen akan ditangani dengan mempertimbangkan itu.

Saya ingin merekam panggilan ke /usr/bin/do_stuff(dan daftar lengkap argumen). Jika tidak ada symlink, saya hanya akan pindah do_stuffke do_stuff_realdan menulis skrip

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

Namun, seperti yang saya tahu bahwa itu akan memeriksa nama yang dipanggil, ini tidak akan berhasil. Bagaimana cara menulis skrip untuk mencapai yang sama tetapi masih meneruskan ke do_stuff'nama yang dapat dieksekusi'

Sebagai catatan, untuk menghindari jawaban pada baris berikut:

  • Saya tahu bahwa saya bisa melakukannya dalam C (menggunakan execve), tetapi akan jauh lebih mudah jika saya bisa, dalam hal ini, cukup gunakan skrip shell.
  • Saya tidak bisa begitu saja mengganti do_stuffdengan program logging.
Neil Townsend
sumber

Jawaban:

6

Anda sering melihat ini dalam hal utilitas seperti busybox, sebuah program yang dapat menyediakan sebagian besar utilitas unix umum dalam satu executable, yang berperilaku berbeda tergantung pada pemanggilannya / busyboxdapat melakukan banyak fungsi, acpidmelalui zcat.

Dan itu biasanya memutuskan apa yang seharusnya dilakukan dengan melihat argv[0]parameternya main(). Dan itu seharusnya bukan perbandingan sederhana. Karena argv[0]mungkin sesuatu seperti itu sleep, atau mungkin /bin/sleepdan harus memutuskan untuk melakukan hal yang sama. Dengan kata lain, jalan akan membuat segalanya lebih kompleks.

Jadi, jika hal itu dilakukan oleh program pekerja dengan benar, pembungkus logging Anda dapat mengeksekusi dari sesuatu seperti /bin/realstuff/make_teadan jika pekerja hanya melihat nama argv[0]samaran, maka fungsi yang benar harus dijalankan.

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

Dalam contoh di atas, argv[0]harus membaca sesuatu seperti /tmp/MYEXEC4321/make_tea(jika 4321 adalah PID untuk /bin/shyang berlari) yang seharusnya memicu make_teaperilaku nama samaran

Jika Anda ingin argv[0]menjadi salinan persis dari apa itu tanpa bungkusnya, Anda memiliki masalah yang lebih sulit. Karena jalur file absolut dimulai dengan /. Anda tidak dapat membuat yang baru /bin/sleep (absen chrootdan saya pikir Anda tidak ingin pergi ke sana). Seperti yang Anda perhatikan, Anda bisa melakukannya dengan sedikit rasa exec(), tetapi itu bukan pembungkus kulit.

Sudahkah Anda mempertimbangkan untuk menggunakan alias untuk menekan logger dan kemudian memulai program dasar alih-alih pembungkus skrip? Itu hanya akan menangkap serangkaian acara terbatas, tapi mungkin itu adalah satu-satunya acara yang Anda pedulikan

diinfiks
sumber
Harus mengganti basename dengan sed, tetapi jika tidak berfungsi dengan baik.
Neil Townsend
1
@NeilTownsend, dengan POSIX sh, Anda bisa menggunakan mybase=${0##*/}bukannya nama.
Stéphane Chazelas
@ StéphaneChazelas basenameDoa seperti basename -- foo.bartidak saya kenal, dan pada sistem Linux saya mengujinya menghasilkan hasil foo.baryang akan memecahkan skrip. Apakah Anda yakin itu metode yang umum?
diinfiks
basename -- foo.barpengembalian foo.bar, basename -- --foo--/barpengembalian barseperti yang diharapkan. basename "$foo"hanya berfungsi jika Anda dapat menjamin $footidak dimulai dengan a -. Sintaks umum untuk menjalankan perintah dengan argumen arbitrer adalah cmd -x -y -- "$argument". cmd "$argument"salah kecuali maksudmu cmd "$option_or_arg". Sekarang, beberapa perintah (termasuk beberapa implementasi historis basename) tidak mendukung --sehingga Anda terkadang harus memilih antara portabilitas dan keandalan.
Stéphane Chazelas
6

Anda dapat menggunakan exec -a(seperti yang ditemukan dalam bash, ksh93, zsh, mksh, yashtapi tidak POSIX belum) yang digunakan untuk menentukan argv[0]perintah yang sedang dieksekusi:

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

Perhatikan bahwa $0adalah tidak yang argv[0]bahwa perintah menerima. Ini adalah jalur skrip sebagaimana diteruskan ke execve()(dan yang diteruskan sebagai argumen bash), tetapi itu mungkin cukup untuk tujuan Anda.

Sebagai contoh, jika make_teadipanggil sebagai:

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

Seperti yang biasanya dilakukan shell ketika menjalankan perintah dengan nama (mencari executable in $PATH), wrapper akan:

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

Itu bukan:

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

tapi itu cukup baik karena do_stuff_realtahu itu dimaksudkan untuk membuat teh.

Di mana itu akan menjadi masalah adalah jika do_stuffdipanggil sebagai:

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

karena itu akan diterjemahkan ke:

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

Itu tidak akan terjadi selama operasi normal, tetapi perhatikan bahwa pembungkus kami melakukan sesuatu seperti itu.

Pada kebanyakan sistem, argv[0]sebagai diteruskan ke script hilang setelah juru (di sini /bin/bash) dijalankan ( yang argv[0]dari interpreter pada kebanyakan sistem adalah jalan yang diberikan pada baris dia-bang ) sehingga tidak ada script shell dapat lakukan tentang hal itu.

Jika Anda ingin meneruskannya argv[0], Anda harus mengkompilasi executable. Sesuatu seperti:

#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
sumber
+1: Ini akan menjadi jawaban yang tepat, kecuali bahwa shell pada sistem yang saya coba kerjakan tidak memberikan opsi -a untuk eksekutif ...
Neil Townsend
@NeilTownsend Anda dapat melakukan sesuatu yang serupa perlatau pythonjika tersedia. Versi yang lebih lama zshtidak mendukung exec -a, tetapi Anda selalu dapat menggunakannya ARGV0=the-argv-0 cmd args.
Stéphane Chazelas
Ini adalah mesin berbasis busybox, jadi cangkangnya kelihatannya abu, yang sepertinya tidak memiliki flag -a. Atau, setidaknya, pada versi ini. Karena saya sedang dalam proses mengerjakan firmware, dan saya tidak memiliki pegangan yang cukup jelas untuk melakukan sesuatu yang lancang, saya mencoba untuk menghindari menambahkan terlalu banyak untuk itu hanya untuk memahami bagaimana itu dimulai. Saya pikir ARGV0 adalah satu-satunya zsh?
Neil Townsend
@NeilTownsend, ya itu hanya zsh-only. Maksud saya jika Anda memiliki zsh(meskipun sekarang Anda sudah menjelaskan bahwa Anda belum) tetapi itu terlalu tua, Anda masih bisa menggunakannya di ARGV0sana.
Stéphane Chazelas
Cukup adil - Jika saya bisa mencentang jawaban Anda untuk menjadi 'brilian tetapi tidak benar-benar bekerja untuk situasi saya yang tidak jelas "Saya akan.
Neil Townsend