Shell – How to log calls using a wrapper script when there are multiple symlinks to the executable

shell-script

Long story short: I would like to track the way in which some executables are called to track some system behaviour. Let's say that I have an executable:

/usr/bin/do_stuff

And it is actually called by a number of different names via symlink:

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

and so on. Clearly, do_stuff is going to use the first argument it receives to determine what action is actually takes, and the rest of the arguments will be handled in the light of that.

I would like to record ever call to /usr/bin/do_stuff (and the full list of arguments). If there were no symlinks, I would simply move do_stuff to do_stuff_real and write a script

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

However, as I know that it will examine the name that it is called by, this won't work. How does one write a script to achieve the same but still pass on to do_stuff the right 'executable used name'?

For the record, to avoid answers on these lines:

  • I know that I can do it in C (using execve), but it would be a lot easier if I could, in this case, just use a shell script.
  • I can't simply replace do_stuff with a logging programme.

Best Answer

You often see this in case of utilities like busybox, a program that can provide most of the common unix utilities in one executable, that behaves different depending on its invocation/ busybox can do a whole lot of functions, acpid through zcat.

And it commonly decides what it's supposed to be doing by looking at it's argv[0] parameter to main(). And that shouldn't be a simple comparison. Because argv[0] might be something like sleep, or it might be /bin/sleep and it should decide to do the same thing. In other words, the path is going to make things more complex.

So if things were done by the worker program right, your logging wrapper could execute from something like /bin/realstuff/make_tea and if the worker looks at argv[0] basename only, then the right function should execute.

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

In the example above, argv[0] should read something like /tmp/MYEXEC4321/make_tea (if 4321 was the PID for the /bin/sh that ran)which should trigger the basename make_tea behavior

If you want argv[0] to be an exact copy of what it would be without the wrapper, you have a tougher problem. Because of absolute file paths beginning with /. You can't make a new /bin/sleep (absent chroot and I don't think you want to go there). As you note, you could do that with some flavor of exec(), but it wouldn't be a shell wrapper.

Have you considered using an alias to hit the logger and then start the base program instead of a script wrapper? It'd only catch a limited set of events, but maybe those are the only events you care about

Related Question