8

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.
6

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

  • Had to replace basename with sed, but otherwise works well. – Neil Townsend Jun 7 '16 at 19:49
  • 1
    @NeilTownsend, with a POSIX sh, you can use mybase=${0##*/} instead of basename. – Stéphane Chazelas Jun 7 '16 at 19:54
  • @StéphaneChazelas A basename invocation like basename -- foo.bar is unfamiliar to me, and on the Linux system I tested it on produces the result foo.bar which would break the script. Are you sure that's a common method? – infixed Jun 7 '16 at 20:22
  • basename -- foo.bar returns foo.bar, basename -- --foo--/bar returns bar as expected. basename "$foo" only works if you can guarantee $foo doesn't start with a -. The general syntax to invoke a command with arbitrary arguments is cmd -x -y -- "$argument". cmd "$argument" is wrong unless you mean cmd "$option_or_arg". Now, some commands (including some historic implementations of basename) don't support -- so you may sometimes have to choose between portability and reliability. – Stéphane Chazelas Jun 7 '16 at 20:49
6

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;
}
  • +1: This would be the right answer, except that the shell on the system I'm trying to work on doesn't provide the -a option for exec ... – Neil Townsend Jun 7 '16 at 19:31
  • @NeilTownsend You could do something similar with perl or python if available. Older versions of zsh didn't support exec -a, but you can always use ARGV0=the-argv-0 cmd args instead. – Stéphane Chazelas Jun 7 '16 at 19:46
  • It's a busybox based machine, so the shell seems to be ash, which doesn't seem to have the -a flag. Or, at least, on this version. As I'm in the process of working on the firmware, and I don't have a clear enough grip on it to do anything presumptuous, I am trying to avoid adding too much to it just to understand how it starts up. I think ARGV0 is a zsh only thing? – Neil Townsend Jun 7 '16 at 19:57
  • @NeilTownsend, yes that's a zsh-only thing. I meant that if you had zsh (though now you've made it clear that you haven't) but it was too old, you could still use ARGV0 there. – Stéphane Chazelas Jun 7 '16 at 20:51
  • Fair enough - If I could tick your answer for being 'brilliant but didn't actually work for my obscure situation" I would. – Neil Townsend Jun 8 '16 at 7:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.