Cách ghi nhật ký cuộc gọi bằng cách sử dụng tập lệnh bao bọc khi có nhiều liên kết tượng trưng để thực thi


8

Câu chuyện dài: Tôi muốn theo dõi cách thức mà một số thực thi được gọi để theo dõi một số hành vi hệ thống. Hãy nói rằng tôi có một thực thi:

/usr/bin/do_stuff

Và nó thực sự được gọi bằng một số tên khác nhau thông qua symlink:

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

và như thế. Rõ ràng, do_stuffsẽ sử dụng đối số đầu tiên mà nó nhận được để xác định hành động nào thực sự được thực hiện và phần còn lại của các đối số sẽ được xử lý theo hướng đó.

Tôi muốn ghi lại bao giờ gọi đến /usr/bin/do_stuff(và danh sách đầy đủ các đối số). Nếu không có liên kết tượng trưng, ​​tôi chỉ cần chuyển do_stuffđến do_stuff_realvà viết một kịch bản

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

Tuy nhiên, như tôi biết rằng nó sẽ kiểm tra tên mà nó được gọi bởi, điều này sẽ không hoạt động. Làm thế nào để một người viết một kịch bản để đạt được điều tương tự nhưng vẫn chuyển sang do_stuffđúng 'tên được sử dụng thực thi'?

Đối với bản ghi, để tránh câu trả lời trên các dòng này:

  • Tôi biết rằng tôi có thể làm điều đó trong C (sử dụng execve), nhưng sẽ dễ dàng hơn rất nhiều nếu tôi có thể, trong trường hợp này, chỉ cần sử dụng một tập lệnh shell.
  • Tôi không thể đơn giản thay thế do_stuffbằng một chương trình đăng nhập.

Câu trả lời:


6

Bạn thường thấy điều này trong trường hợp các tiện ích như busybox, một chương trình mà có thể cung cấp hầu hết các tiện ích Unix phổ biến ở một thực thi, mà cư xử khác nhau tùy thuộc vào gọi của nó / busyboxcó thể làm một toàn bộ rất nhiều chức năng, acpidthông qua zcat.

Và nó thường quyết định những gì nó phải làm bằng cách nhìn vào argv[0]tham số của nó main(). Và đó không nên là một so sánh đơn giản. Bởi vì argv[0]có thể là một cái gì đó giống như sleep, hoặc nó có thể /bin/sleepvà nó sẽ quyết định làm điều tương tự. Nói cách khác, con đường sẽ làm cho mọi thứ phức tạp hơn.

Vì vậy, nếu mọi thứ được thực hiện bởi chương trình worker, trình bao bọc ghi nhật ký của bạn có thể thực thi từ một cái gì đó giống như /bin/realstuff/make_teavà nếu worker chỉ nhìn vào argv[0]tên cơ sở, thì hàm bên phải sẽ thực thi.

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

Trong ví dụ trên, argv[0]nên đọc một cái gì đó như /tmp/MYEXEC4321/make_tea(nếu 4321 là PID cho lần /bin/shchạy đó) sẽ kích hoạt make_teahành vi tên cơ sở

Nếu bạn muốn argv[0]trở thành một bản sao chính xác của những gì nó sẽ không có trình bao bọc, bạn có một vấn đề khó khăn hơn. Bởi vì đường dẫn tập tin tuyệt đối bắt đầu bằng /. Bạn không thể tạo một cái mới /bin/sleep (vắng mặt chrootvà tôi không nghĩ bạn muốn đến đó). Như bạn lưu ý, bạn có thể làm điều đó với một số hương vị của exec(), nhưng nó sẽ không phải là một vỏ bọc.

Bạn đã cân nhắc sử dụng một bí danh để nhấn logger và sau đó khởi động chương trình cơ sở thay vì trình bao bọc tập lệnh chưa? Nó chỉ bắt được một nhóm sự kiện giới hạn, nhưng có lẽ đó là những sự kiện duy nhất bạn quan tâm


Phải thay thế tên cơ sở bằng sed, nhưng nếu không thì hoạt động tốt.
Neil Townsend

1
@NeilTownsend, với POSIX sh, bạn có thể sử dụng mybase=${0##*/}thay vì tên cơ sở.
Stéphane Chazelas

@ StéphaneChazelas Một basenamelời mời giống như basename -- foo.barxa lạ với tôi, và trên hệ thống Linux, tôi đã thử nghiệm nó để tạo ra kết quả foo.barsẽ phá vỡ tập lệnh. Bạn có chắc chắn đó là một phương pháp phổ biến?
vào

basename -- foo.bartrả lại foo.bar, basename -- --foo--/bartrả lại barnhư mong đợi. basename "$foo"chỉ hoạt động nếu bạn có thể đảm bảo $fookhông bắt đầu bằng a -. Cú pháp chung để gọi một lệnh với các đối số tùy ý là cmd -x -y -- "$argument". cmd "$argument"là sai trừ khi bạn có ý nghĩa cmd "$option_or_arg". Bây giờ, một số lệnh (bao gồm cả một số triển khai lịch sử basename) không hỗ trợ --nên đôi khi bạn có thể phải chọn giữa tính di động và độ tin cậy.
Stéphane Chazelas

6

Bạn có thể sử dụng exec -a(như được tìm thấy trong bash, ksh93, zsh, mksh, yashnhưng không phải POSIX chưa) được sử dụng để xác định một argv[0]đến một lệnh được thực thi:

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

Lưu ý rằng điều đó $0không những argv[0]có lệnh nhận được. Đó là đường dẫn của kịch bản được chuyển đến execve()(và được truyền dưới dạng đối số bash), nhưng điều đó có thể là đủ cho mục đích của bạn.

Ví dụ, nếu make_teađược gọi là:

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

Như một shell thường làm khi gọi lệnh theo tên (tra cứu thực thi trong $PATH), trình bao bọc sẽ:

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

Đó không phải là:

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

Nhưng nó đủ tốt để do_stuff_realbiết nó có nghĩa là để pha trà.

Trường hợp đó sẽ là một vấn đề là nếu do_stuffđược gọi là:

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

như điều đó sẽ được dịch thành:

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

Điều đó sẽ không xảy ra trong các hoạt động bình thường, nhưng lưu ý rằng trình bao bọc của chúng tôi làm điều gì đó tương tự.

Trên hầu hết các hệ thống, các argv[0]như truyền cho kịch bản bị mất khi người phiên dịch (ở đây /bin/bash) được thực hiện ( các argv[0]của thông dịch viên trên hầu hết các hệ thống là con đường đưa ra trên dòng cô-bang ) nên không có gì một kịch bản shell có thể làm gì về nó.

Nếu bạn muốn vượt qua argv[0], bạn cần phải biên dịch một tệp thực thi. Cái gì đó như:

#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: Đây sẽ là câu trả lời đúng, ngoại trừ phần vỏ trên hệ thống tôi đang cố gắng không cung cấp tùy chọn -a cho người thực thi ...
Neil Townsend

@NeilTownsend Bạn có thể làm điều gì đó tương tự với perlhoặc pythonnếu có. Các phiên bản cũ hơn zshkhông hỗ trợ exec -a, nhưng bạn luôn có thể sử dụng ARGV0=the-argv-0 cmd argsthay thế.
Stéphane Chazelas

Đó là một máy dựa trên busybox, vì vậy vỏ dường như là tro, dường như không có cờ -a. Hoặc, ít nhất, trên phiên bản này. Vì tôi đang trong quá trình làm việc với phần sụn và tôi không hiểu rõ về nó để làm bất cứ điều gì tự phụ, tôi đang cố gắng tránh thêm quá nhiều vào nó chỉ để hiểu cách nó khởi động. Tôi nghĩ ARGV0 là một điều duy nhất zsh?
Neil Townsend

@NeilTownsend, vâng, đó chỉ là một thứ zsh. Tôi có nghĩa là nếu bạn đã có zsh(mặc dù bây giờ bạn đã nói rõ rằng bạn không có) nhưng nó đã quá cũ, bạn vẫn có thể sử dụng ARGV0ở đó.
Stéphane Chazelas

Đủ công bằng - Nếu tôi có thể đánh dấu câu trả lời của bạn là 'tuyệt vời nhưng thực sự không hoạt động cho tình huống mơ hồ của tôi "Tôi sẽ làm thế.
Neil Townsend
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.