How to log calls using a wrapper script when there are multiple symlinks to the executable
You can use exec -a
(as found in bash
, ksh93
, zsh
, mksh
, yash
but not POSIX yet) that is used to specify an argv[0]
to a command being executed:
#! /bin/bash -
printf '%s\n' "$0 $*" >> /some/log
exec -a "$0" /usr/bin/do_stuff_real "$@"
Note that that $0
is not the argv[0]
that the command receives. It is the path of the script as passed to execve()
(and which is passed as argument to bash
), but that's likely to be enough for your purpose.
As an example, if make_tea
was invoked as:
execv("/usr/bin/make_tea", ["make_tea", "--sugar=2"])
As a shell would typically do when invoking the command by name (looking up the executable in $PATH
), the wrapper would to:
execv("/usr/bin/do_stuff_real", ["/usr/bin/make_tea", "--sugar=2"])
That's not:
execv("/usr/bin/do_stuff_real", ["make_tea", "--sugar=2"])
but that's good enough as do_stuff_real
knows it's meant to make tea.
Where that would be a problem is if do_stuff
was invoked as:
execv("/usr/bin/do_stuff", ["/usr/bin/make_tea", "--sugar=2"])
as that would be translated to:
execv("/usr/bin/do_stuff_real", ["/usr/bin/do_stuff", "--sugar=2"])
That would not happen during normal operations, but note that our wrapper does something like that.
On most systems, the argv[0]
as passed to the script is lost once the interpreter (here /bin/bash
) is executed (the argv[0]
of the interpreter on most systems is the path given on the she-bang line) so there's nothing a shell script can do about it.
If you wanted to pass the argv[0]
along, you'd need to compile an executable. Something like:
#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;
}
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