[Toybox] "bad xform" not very helpful

Rob Landley rob at landley.net
Thu Sep 1 02:35:24 PDT 2022


On 8/30/22 03:59, David Seikel wrote:
> I'm good at reading code, but I know you are not.

As demonstrated below.

On 8/31/22 20:33, David Seikel wrote:
> On 2022-08-31 18:18:15, enh via Toybox wrote:
>>    it does by default. that's what i turned off as a workaround for this bug.
> 
> Um how does one using toybox as the foundation for something else
> actually call any random toy directly?  Is there a vararg calltoy()
> function I missed?

You know how we're calling xexec() everywhere?

  $ grep -l xexec main.c lib/*.c toys/*/*.c | wc -l
  30

That does:

  // Only recurse to builtin when we have multiplexer and !vfork context.
  if (CFG_TOYBOX && !CFG_TOYBOX_NORECURSE)
    if (toys.stacktop && !strchr(*argv, '/')) toy_exec(argv);
  execvp(argv[0], argv);

That first part is calling toy_exec() out of main.c, and falling back to
execvp() if it can't (or when it fails).

There's a lot of sanity checks in toy_exec() and the things it calls, checking
if we need to re-exec to acquire SUID privileges, and measuring stack depth and
refusing to recurse if it's gone too deep. But that's not actually required, the
actual the actual CALL to the other command is just this bit at the end, lightly
cleaned up to be usable out of context:

  // Run command
  toy_init(toy_find(*argv), argv);
  if (toys.which) {
    toys.which->toy_main();
    xexit();
  }

You could also look at main() at the end of main.c to find the standalone init:

    // single command built standalone with no multiplexer is first list entry
    toy_singleinit(toy_list, argv);
    toy_list->toy_main();

(Using the first entry in toy_list instead of having toy_find() binary search
for a neame...)

Or in toybox_main() there's this bit:

    char *ss = basename(s);
    struct toy_list *tl = toy_find(ss);

    if (tl==toy_list && s!=toys.argv[1]) unknown(ss);
    toy_exec_which(tl, toys.argv+1);

All toy_exec() does is call toy_find() and then call toy_exec_which() with the
toy_list pointer instead of the name, but that splits them so it can add a
special error check in between to prevent "toybox toybox toybox toybox toybox
ls" from being recognized.

I didn't think starting at "main()" or reading the xexec() implementation in
lib/ was an unreasonably high bar? There are other less obvious places you could
have found it.

This "which = toy_find(); toy_singleinit(which, argv); which->toy_main();" trio
is also done manually in sh.c to implement shell builtins, but I wouldn't expect
you to find that on a casual browse without knowing what you're looking for.

The xpopen()/xrun() family of functions funnel into xpopen_both(), which is also
a bit much to read (all that juggling pipes and nommu support), but it both
calls xexec() and does:

      toy_init(toys.which, toys.argv);
      toys.stacktop = 0;
      toys.which->toy_main();
      xexit();

to implement the "call ourselves again" case where argv is NULL when fork() is
available. (No toy_find() there, it re-uses the existing toys.which to just
recursively call the same command. The toys.stacktop is both to disable further
recursion and to signal to the re-entered command that this is not the first
time it's been called, but that has to do with command reentry, ala this bit of
cpio.c:

    if (toys.stacktop) {
      // xpopen() doesn't return from child due to vfork(), instead restarts
      // with !toys.stacktop
      pid = xpopen(0, &pipe, 0);
      afd = pipe;
    } else {
      // child
      toys.optflags |= FLAG_i;
      xchdir(*toys.optargs);
    }

Or this bit of tar.c:

        // Fork a copy of ourselves to handle extraction (reads from zip output
        // pipe, writes to stdout).
        pipefd[0] = pipefd[1];
        pipefd[1] = 1;
        pid = xpopen_both(0, pipefd);
        close(pipefd[1]);

(This is because nommu systems can't do fork(), and vfork() requires calling
exec() to unblock the parent, so xopen() can take a NULL argv to re-exec
ourselves, in which case the parent will have a non-null toys.stacktop when it
enters command_main() and the child will have a null one. The xpopen pipe(s) let
the parent feed the child whatever info it needs; you can also do it through
inherited environment variables.)

See also:
https://landley.net/toybox/code.html#running

And:
https://landley.net/toybox/code.html#:~:text=The%20following%20functions

Sorry I haven't done a video walkthrough of this part yet. Every time I look at
prudetube it hits me with a fresh round of:

https://youtu.be/IPXukSZhTuI
https://youtu.be/punLD6zM2bI
https://youtu.be/IdE_ADys95c
https://youtu.be/zOV1DDMenrU

And I go "maybe I should find another video hosting solution"...

Rob


More information about the Toybox mailing list