Previous Section  < Day Day Up >  Next Section

Appendix C. Loadable Built-Ins

bash 2.0 introduced a new feature that increased the flexibility of the shell: dynamically loadable built-ins. On systems that support dynamic loading, you can write your own built-ins in C, compile them into shared objects, and load them at any time from within the shell with the enable built-in (see Chapter 7 for details on all of the enable options).

This appendix will discuss briefly how to go about writing a built-in and loading it in bash. The discussion assumes that you have experience with writing, compiling, and linking C programs.

The bash archive contains a number of pre-written built-ins in the directory examples/loadables/. You can build them by uncommenting the lines in the file Makefile that are relevent to your system, and typing make. We'll take one of these built-ins, tty, and use it as a "case study" for built-ins in general.

tty will mimic the standard UNIX command tty. It will print the name of the terminal that is connected to standard input. The built-in will, like the command, return true if the device is a TTY and false if it isn't. In addition, it will take an option, -s, which specifies that it should work silently, i.e., print nothing and just return a result.

The C code for a built-in can be divided into three distinct sections: the code that implements the functionality of the built-in, a help text message definition, and a structure describing the built-in so that bash can access it.

The description structure is quite straightforward and takes the form:

struct builtin structname = {

    "builtin_name",

    function_name,

    BUILTIN_ENABLED,

    help_array,

    "usage",

    0

};

builtin_name is the name of the built-in as it appears in bash. The next field, function-name, is the name of the C function that implements the built-in. We'll look at this in a moment. BUILTIN_ENABLED is the initial state of the built-in, whether it is enabled or not. This field should always be set to BUILTIN_ENABLED. help_array is an array of strings which are printed when help is used on the built-in. usage is the shorter form of help; the command and its options. The last field in the structure should be set to 0.

In our example we'll call the built-in tty, the C function tty_builtin, and the help array tty_doc. The usage string will be tty [-s]. The resulting structure looks like this:

struct builtin tty_struct = {

    "tty",

    tty_builtin,

    BUILTIN_ENABLED,

    tty_doc,

    "tty [-s]",

    0

};

The next section is the code that does the work. It looks like this:

tty_builtin (list)

    WORD_LIST *list;

{

    int opt, sflag;

    char *t;

     

    reset_internal_getopt ( );

    sflag = 0;

    while ((opt = internal_getopt (list, "s")) != -1)

    {

      switch (opt)

      {

          case 's':

              sflag = 1;

              break;

          default:

              builtin_usage ( );

              return (EX_USAGE);

      }

    }

    list = loptend;

     

    t = ttyname (0);

    if (sflag == 0)

        puts (t ? t : "not a tty");

    return (t ? EXECUTION_SUCCESS : EXECUTION_FAILURE);

}

Built-in functions are always given a pointer to a list of type WORD_LIST. If the built-in doesn't actually take any options, you must call no_options(list) and check its return value before any further processing. If the return value is non-zero, your function should immediately return with the value EX_USAGE.

You must always use internal_getopt rather than the standard C library getopt to process the built-in options. Also, you must reset the option processing first by calling reset_internal_getopt.

Option processing is performed in the standard way, except if the options are incorrect, in which case you should return EX_USAGE. Any arguments left after option processing are pointed to by loptend. Once the function is finished, it should return the value EXECUTION_SUCCESS or EXECUTION_FAILURE.

In the case of our tty built-in, we then just call the standard C library routine ttyname, and if the -s option wasn't given, print out the name of the tty (or "not a tty" if the device wasn't). The function then returns success or failure, depending upon the result from the call to ttyname.

The last major section is the help definition. This is simply an array of strings, the last element of the array being NULL. Each string is printed to standard output when help is run on the built-in. You should, therefore, keep the strings to 76 characters or less (an 80-character standard display minus a 4-character margin). In the case of tty, our help text looks like this:

char *tty_doc[] = {

  "tty writes the name of the terminal that is opened for standard",

  "input to standard output.  If the `-s' option is supplied, nothing",

  "is written; the exit status determines whether or not the standard",

  "input is connected to a tty.",

  (char *)NULL

};

The last things to add to our code are the necessary C header files. These are stdio.h and the bash header files config.h, builtins.h, shell.h, and bashgetopt.h.

Here is the C program in its entirety:

#include "config.h"

#include <stdio.h>

#include "builtins.h"

#include "shell.h"

#include "bashgetopt.h"

     

     

extern char *ttyname ( );

     

tty_builtin (list)

    WORD_LIST *list;

{

    int opt, sflag;

    char *t;

     

    reset_internal_getopt ( );

    sflag = 0;

    while ((opt = internal_getopt (list, "s")) != -1)

    {

        switch (opt)

        {

            case 's':

                sflag = 1;

                break;

            default:

                builtin_usage ( );

                return (EX_USAGE);

        }

    }

    list = loptend;

     

    t = ttyname (0);

    if (sflag == 0)

        puts (t ? t : "not a tty");

    return (t ? EXECUTION_SUCCESS : EXECUTION_FAILURE);

}

     

char *tty_doc[] = {

    "tty writes the name of the terminal that is opened for standard",

    "input to standard output.  If the `-s' option is supplied, nothing",

    "is written; the exit status determines whether or not the standard",

    "input is connected to a tty.",

    (char *)NULL

};

     

struct builtin tty_struct = {

    "tty",

    tty_builtin,

    BUILTIN_ENABLED,

    tty_doc,

    "tty [-s]",

    0

};

We now need to compile and link this as a dynamic shared object. Unfortunately, different systems have different ways to specify how to compile dynamic shared objects. Table C-1 lists some common systems and the commands needed to compile and link tty.c. Replace archive with the path of the top level of the bash archive.

Table C-1. Shared object compilation

System

Commands

SunOS 4

cc -pic -Iarchive -Iarchive/builtins -Iarchive/lib -c tty.c

 

ld -assert pure-text -o tty tty.o

SunOS 5

cc -K pic -Iarchive -Iarchive/builtins -Iarchive/lib -c tty.c

 

cc -dy -z text -G -i -h tty -o tty tty.o

SVR4, SVR4.2, Irix

cc -K PIC -Iarchive -Iarchive/builtins -Iarchive/lib -c tty.c

 

ld -dy -z text -G -h tty -o tty tty.o

AIX

cc -K -Iarchive -Iarchive/builtins -Iarchive/lib -c tty.c

 

ld -bdynamic -bnoentry -bexpall -G -o tty tty.o

Linux

cc -fPIC -Iarchive -Iarchive/builtins -Iarchive/lib -c tty.c

 

ld -shared -o tty tty.o

NetBSD, FreeBSD

cc -fpic -Iarchive -Iarchive/builtins -Iarchive/lib -c tty.c

 

ld -x -Bshareable -o tty tty.o


After you have compiled and linked the program, you should have a shared object called tty. To load this into bash, just type enable -f path/tty tty, where path is the full pathname of the shared object. You can remove a loaded built-in at any time with the -d option, e.g., enable -d tty.

You can put as many built-ins as you like into one shared object; all you need are the three main sections that we saw above for each built-in in the same C file. It is best, however, to keep the number of built-ins per shared object small. You will also probably find it best to keep similar built-ins, or built-ins that work together (e.g., pushd, popd, dirs), in the same shared object.

bash loads a shared object as a whole, so if you ask it to load one built-in from a shared object that has twenty built-ins, it will load all 20 (but only one will be enabled). For this reason, keep the number of built-ins small to save loading memory with unnecessary things, and group similar built-ins so that if the user enables one of them, all of them will be loaded and ready in memory for enabling.

    Previous Section  < Day Day Up >  Next Section