[patch] Not quite bbsh. :)

Rob Landley rob at landley.net
Sat Sep 2 19:30:31 UTC 2006


On Saturday 02 September 2006 12:36 pm, Bernhard Fischer wrote:
> On Fri, Sep 01, 2006 at 05:20:45PM -0400, Rob Landley wrote:
> >So here's a little toy I made; the world's most pathetic shell.  It's an 
> >experiment to see how small I can get a shell down to, and it does indeed 
> >beat lash in both size and lameness.
> 
> >+static int handle(char *command)
> >+{
> [snip]
> >+	if (!argc) return 0;
> >+	if (argc==2 && !strcmp(argv[0],"cd")) chdir(argv[1]);
> 
> How should we deal with "builtin" commands?

Still working that out. :)

I'm leaning towards the lash method.  Here I was just implementing the 
absolute essentials (cd and exit) quick-and-dirty, and inline if statements 
were the fastest way to do that.  Some things you can't unload on a child 
process because the point of them is to mess with your current process's 
state.  A lot of stuff like builtin echo is just a performance optimization 
which can be handled via nofork versions of standard busybox applets.  
But "cd" doesn't make sense as a child process, you _can't_ fork and make 
that work.

> Should we possibly (optionally) provide "cd" et al as applets?

I was thinking about that yesterday.  There are two downsides:

First, I want scripts/individual to be able to build a usable version of this 
thing.  Relying on the applet infrastructure for builtins makes this harder.

Secondly, we'd have to teach the main applet mechanism not just about NOFORK, 
but also that there are some applets you don't want to make symlinks for.  
Things like "cd", "read", "exit", "exec", "export", and "source" (also known 
as ".") make no sense whatosever as separate processes, because what they do 
goes away when the new process does.  (Things like "echo" and "test" can be 
run with nofork, but also make sense as a standalone applet.)

Lash didn't try to integrate its builtins into the main applet infrastructure, 
instead it just made its own builtin function table.  The question is: is it 
smaller (and simpler) to teach busybox's applet.c about stuff that you 
don't --install (and get it out of busybox.links too, and figure out whether 
it should be listed in BusyBox.html as a separate entry or grouped under 
bbsh)...  Or is it easier to just make another array of function pointers 
with attached help text?  (Keep in mind I want to teach run_applet_by_name() 
about NOFORK anyway.  If we've got a perfectly usable echo or test, I don't 
want to have to re-implement it for speed purposes.  Note that NOFORK implies 
ENABLE_FEATURE_CLEAN_UP actually _happens_ in that applet.  Wonder why I've 
cared about that?  We might want to figure out how to enable it on a 
per-applet basis, or perhaps ENABLE_FEATURE_CLEAN_UP || ENABLE_BBSH)

I'd probably be more interested in trying to make inline if() statements scale 
if it wasn't for the help thing.  Even lash has a "help" entry that lists 
available commands.  It should list all the builtin busybox applets, plus any 
builtin local applets (like cd and export).  On the other hand, if we want 
the help entry for bbsh to list its built-ins anyway, possibly recycling 
the --help mechanism for that makes sense?

You wonder why it's taking me so long to design all this?

> Theoretical nitpicks about the codeline above:
> 1) The strcmp call is much larger than comparing these by hand

There's a certain amount of "quick and dirty" here.  I wasn't trying to find 
the most optimized way of doing this, I was trying to figure out what the 
least amount of work it needed to do was.

Also, the builtins will probably get moved out to something more like the lash 
builtin_xxx function table, I haven't quite decided yet.  (Depends on what 
kind of impact environment variable handling requires.  That part's still 
wet, as it were.)

It's easier to configure down a larger table of builtins to have just a couple 
of entries than it is to have two different code paths depending on whether 
we're being big or small.  If I got to the table, then everything goes to the 
table.  (Can the if() else if() else tree be made to scale?  At what point 
does it get bigger than the table version?  How big is the extra call to 
strcmp() compared to the overhead of a function call?  Can I get away with 
making the current argc/argv pair and the return value globals so these 
functions can be void blah(void) and avoid any stack setup at all?  If I did, 
would it be a good idea from a maintenance perspective?)

> 2) so if i invoke cdplayer /my/music_repo/*.au  then you do what
> exactly? ;)

Currently?  Feed cdplayer a filename with an * in it.  I mentioned that it 
doesn't handle globbing yet.

Right now, this clump:

        // Grab next word.  (Add dequote and envvar logic here)
        end=start;
        while(*end && !isspace(*end)) end++;
        argv[argc++]=xstrndup(start,end-start);
        start=end;

Is designed to be ripped out and replaced with something more like:

	start += grab_next_word(start, &argv);

And grab_next_word() could easily add more than one entry to argv.  That 
function is where wildcards go (I.E. globbing), and environment variable 
handling, and quote handling, and backslash escapes, and lots of other stuff.

The tricky bit is: that's not enough.  Pipe handling means that the loop 
coverting command into argv[] has to be _within_ a loop, because the result 
isn't just one argv[] but array of them (one per pipe segment) plus 
information about how they feed into each other (redirects).  I'm currently 
trying to put together a nice clean way to get that to #ifdef out so it 
shrinks down to something reasonably close to this small version when you 
don't need it.  That's probably the second code drop. :)

And THAT is the hard part of all this.  Not implementing the functionality, 
but making it cleanly separated enough that fundamental things like pipes and 
redirects can be a compile-time option without resulting it horrible 
duplication separated by unreadable #ifdefs.

I'm working on it.  This would go a lot faster if work didn't want me working 
on other things as my top priority right now.  (Whee!  Three day weekend!)

Right now, I'm trying to figure out what's necessary to teach it pipes and 
redirects.  And _that_ means I have to do more intelligent tokenizing to 
catch "thingy;thingy" with no spaces, but I still want quoting, environment 
variables, and pipelines to be separately selectable config options...

P.S.  Yes, I'm looking at Yann's quoting code in modprobe and trying to figure 
out if I can stick something shared in libbb that both can use... :)

> >+	else if(!strcmp(argv[0],"exit")) exit(argc>1 ? atoi(argv[1]) : 0); 
> >+	else {
> >+		int status;
> >+		pid_t pid=fork();
> >+		if(!pid) {
> >+			run_applet_by_name(argv[0],argc,argv);
> >+			execvp(argv[0],argv);
> >+			printf("No %s",argv[0]);
> >+			exit(1);
> >+		} else waitpid(pid, &status, 0);
> Another theoretical nitpick (can't resist, sorry :)

Go for it.  I opened myself up to this when I posted a code sample. :)

> status is superfluous, better make that read waitpid(pid,NULL,0); and
> ditch it alltogether, at least if no jobcontrol is selected or something
> like this.

Status is currently superfluous, yes.  (That's another of the ragged edges 
where "code plugs in here".  I filed down most of them for this example, but 
missed that one...)

Ok, what cares about exit status (checks notes):
  Job control.
  Environment variable support ($?).
  Flow control (if, while, etc).
  Pipes & Redirects (which covers && || & ; and friends)...

Can I have each of those without the others?  The first three don't depend on 
each other, but I think job control and maybe flow control probably depend on 
pipes and redirects.  Hmmm...

Job control needs to be able to background processes with & to make much 
sense.  Well, I suppose you could use ctrl-z and bg, but that's stretching it 
(and that requires terminal control.  So it's going to depend on 
_something_.)  Plus job control needs the infrastructure for tying together 
processes into a pipeline in order to extend that into manipulating groups of 
jobs with fg and bg and such.

I suppose I can do if statements without &&, so flow control doesn't quite 
have to depend on pipes and redirects.  At the same time, I need _some_ kind 
of structure representing a process in order to save the exit code and use it 
later.  (I have lots of notes on this, but assembling them into dependency 
trees is tricky.)

> [snip]
> Looks pretty small indeed :)

Yup.  That was the point. :)

The actual minimal configuration of bbsh will probably be slightly larger than 
this, just because some things might wind up being a huge pain to configure 
out (like tracking exit status or using a builtin function table).  But this 
is a frame of reference so I know what "small" looks like, and can track 
bloat relative to there.

Rob
-- 
Never bet against the cheap plastic solution.



More information about the busybox mailing list