当有多个指向可执行文件的符号链接时,如何使用包装脚本记录调用


8

长话短说:我想跟踪一些可执行文件的调用方式,以跟踪某些系统行为。假设我有一个可执行文件:

/usr/bin/do_stuff

实际上,它通过符号链接由许多不同的名称调用:

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

等等。显然,do_stuff将使用接收到的第一个参数来确定实际要执行的操作,其余的参数将根据该参数进行处理。

我想记录一下对它的调用/usr/bin/do_stuff(以及完整的参数列表)。如果没有符号链接,我会干脆转移do_stuffdo_stuff_real写一个脚本

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

但是,据我所知,它将检查调用它的名称,这将不起作用。如何编写脚本以实现相同但仍传递给do_stuff正确的“可执行使用名称”?

为了记录在案,请避免出现以下情况的答案:

  • 我知道我可以用C语言(使用execve)来做到这一点,但是如果我可以在这种情况下只使用shell脚本,那会容易得多。
  • 我不能简单地do_stuff用日志记录程序代替。

Answers:


6

在诸如实用程序之类的实用程序的情况下,您经常会看到这种情况busybox,该程序可以在一个可执行文件中提供大多数常见的unix实用程序,其行为根据其调用而有所不同/ busybox可以acpid通过来完成很多功能zcat

通常,它通过查看argv[0]参数to来决定应该做的事情main()。那不应该是一个简单的比较。因为argv[0]可能是sleep,或者可能是/bin/sleep,它应该决定做同样的事情。换句话说,这条道路将使事情变得更加复杂。

因此,如果事情是由工作程序正确完成的,则您的日志记录包装程序可以通过类似的方式执行,/bin/realstuff/make_tea并且如果工作程序argv[0]仅查看基本名称,则应执行正确的功能。

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

在上面的示例中,argv[0]应读取类似内容/tmp/MYEXEC4321/make_tea(如果4321是所/bin/sh运行的PID ),则应触发基本名称make_tea行为

如果要argv[0]成为没有包装纸的确切副本,则会遇到更棘手的问题。由于绝对文件路径以开头/。您不能制作一个新的/bin/sleep (缺席chroot,我不认为您想去那里)。正如您所注意到的,您可以使用来实现此目的exec(),但它不是外壳程序。

您是否考虑过使用别名来命中记录器,然后启动基本程序而不是脚本包装器?它只会捕获有限的事件集,但是也许那些是您唯一关心的事件


不得不用sed代替基本名称,但否则效果很好。
尼尔·汤森

1
@NeilTownsend,带有POSIX sh,可以mybase=${0##*/}代替basename 使用。
斯特凡Chazelas

@StéphaneChazelas 我不熟悉basename像这样的调用basename -- foo.bar,并且在Linux系统上我对其进行了测试,产生了foo.bar会破坏脚本的结果。您确定这是一种常用方法吗?
6

basename -- foo.barreturn foo.bar,按预期basename -- --foo--/bar返回barbasename "$foo"仅在您可以保证$foo不是以开头的情况下才有效-。调用带有任意参数的命令的一般语法为cmd -x -y -- "$argument"cmd "$argument"除非你的意思是错误的cmd "$option_or_arg"。现在,某些命令(包括的某些历史实现basename)不支持,--因此有时您可能不得不在可移植性和可靠性之间进行选择。
斯特凡Chazelas

6

您可以使用exec -a(如发现bashksh93zshmkshyash但不是POSIX还),用于指定argv[0]的命令被执行:

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

请注意,这$0不是argv[0]该命令接收。它是脚本传递给的路径execve()(并作为参数传递给bash),但这对于您的目的而言可能就足够了。

例如,if make_tea被调用为:

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

正如外壳程序通常在按名称调用命令(在中查找可执行文件$PATH)时所做的那样,包装程序将:

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

不是:

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

但这足够了,因为do_stuff_real它知道是要泡茶的。

如果do_stuff被调用为:

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

因为它将被翻译为:

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

在正常操作期间不会发生这种情况,但是请注意,我们的包装程序会执行类似的操作。

在大多数系统中,argv[0]作为传递到脚本一次解释(这里丢失/bin/bash)执行(argv[0]大多数系统上的解释是对她邦行给出的路径),这样就没有一个shell脚本可以做这件事。

如果您想传递这些信息argv[0],则需要编译一个可执行文件。就像是:

#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:这将是正确的答案,除了我尝试使用的系统上的shell不为exec提供-a选项...
尼尔·汤森

@NeilTownsend您可以执行类似的操作,perl或者python在可用时执行类似的操作。的旧版本zsh不支持exec -a,但是您可以随时使用ARGV0=the-argv-0 cmd args
斯特凡Chazelas

这是一台基于busybox的机器,因此外壳似乎是ash,似乎没有-a标志。或至少在此版本上。由于我正在处理固件,而且我对固件的掌握不够清楚,无法做任何冒昧的事情,因此,我试图避免对其添加太多内容,只是为了了解它是如何启动的。我认为ARGV0是zsh唯一的东西吗?
尼尔·汤森

@NeilTownsend,是的,这仅是zsh。我的意思是,如果您拥有zsh(尽管现在您已经明确表示还没有),但是它太旧了,您仍然可以ARGV0在那里使用。
斯特凡Chazelas

不够公平-如果我能勾出你的答案是“辉煌,但实际上并没有工作我模糊的情况:”我会的。
尼尔·汤森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.