Writing Custom Builtins for KSH93

I am looking to create some ksh93 extensions using the custom builtin feature.

I can successfully create a builtin function, load it using the builtin -f command and get an output. However, I want to get/set values of KSH variables from within my built-in.

For example, lets say I am creating builtins to interact with a database. I might create the following builtins:

sqlopen  - open a db connection/cursor
sqlclose - close a db connection/cursor
sqlfetch - fetch results from a cursor

These can be used in the following fashion:

sqlopen connection "mydatabase"

sqlopen connection cursor "select first_name, last_name, email from guest"

while sqlfetch cursor first_name last_name email
do
  ## Process the record
done

sqlclose cursor
sqlclose connection

For the above code to work properly, my builtins should be able to read/write the values of the connection, cursor, first_name, last_name and email shell variables.

I have googled around and the documentation on this subject is minimal.
I know we need to use the nval library that is part of the AST package, but am not able to find any sample codes.

Has anyone done anything like this before? A sample code would be awesome! :smiley: Any pointers would be appreciated!

Some years ago I wrote a few ksh93 builtins. One of them can manipulate the PATH variable and similar ones (like MANPATH, etc). I haven't used it for some time now, so I am not sure, if it still works with a current ksh93 binary. But maybe you get an idea, how variables can be manipulated.

/*****************************************************************************
 *
 * $Id$
 *
 * Kornshell 93 builtin function 'setpath'.
 *
 *****************************************************************************/

#include <shell.h>

/*-------------*/
/* definitions */
/*-------------*/

#define DELIMITER     ':'
#define DEFAULTVAR    "PATH"

/*-----------------------------------------*/
/* embedded manpage and options definition */
/*-----------------------------------------*/

static char optget_opts[] =
"[+NAME?setpath - manipulate PATH type variables]"
"[-AUTHOR?Paul Herger <ph@herger.at>]"
"[+DESCRIPTION?\bsetpath\b manipulates $PATH or other PATH type shell "
    "variables.]"
"[+?The names of directories or files can be "
    "appended to the end (option \b-a\b), "
    "prepended to the beginning (option \b-p\b) or "
    "excluded (option  \b-x\b) from the variable. "
    "These options are mutually exclusive.]"
"[+?After these manipulations, duplicate entries can be removed from the "
    "variable if the \b-u\b option is specified. Each first occurence of "
    "duplicate entries is kept while all others are deleted.]"
"[+?If the \b-e\b option is specified, all directories or files that do not "
    "exists are deleted.]"

"[a?Append to the end of the variable]"
"[p?Prepend to the beginning of the variable]"
"[x?Exclude from the variable]"
"[e?Delete from or do not include into the path variable non existing"
    "directories or files]"
"[u?Make elements unique by deleting all but the first occurence of"
    "every directory or file]"
"[v?Use \avname\a instead of $PATH]:[vname]"
"\n"
"\n[path ...]\n"
"\n"
"[+EXIT STATUS?]{"
    "[+0?Completed successfully.]"
    "[+>0?An error occurred.]}"
"[+EXAMPLE?\b$ setpath -aeu /opt/bin /usr/local/bin\b]"
"[+?Both /opt/bin and /usr/local/bin are appended at the end of the shell "
    "variable $PATH, if they are not yet elements of it. If they do not "
    "exist they are not appended to or are deleted from $PATH.]"
"[+?\b$ setpath -xv MANPATH /opt/man\b]"
"[+?/opt/man is deleted from $MANPATH.]"
"[+?\b$ setpath -ue\b]"
"[+?Duplicate entries and entries that do not refer to an existing "
    "filesystem object are deleted from $PATH.]";

/*----------------------*/
/* other error messages */
/*----------------------*/

static char usage[]        = "[{-a|-p|-x}] [-eu] [-v vname] [path ...]";
static char err_internal[] = "internal error";
static char err_mutual[]   = "options -%c and -%c are mutually exclusive";

/*===========================================================================*/
/* Add a copy of a string to a container (set or queue). If the string       */
/* contains delimiters, split the string at the delimiters and add each part */
/* of the string as independent string to the container, thus splitting PATH */
/* type strings in their components.                                         */
/*===========================================================================*/

static void add_to_container(Dt_t *container, char *string)
{
    char *copy, *ptr;

    if (!(copy = stkcopy(stkstd, string))) return;

    dtinsert(container, copy);

    for (ptr = copy; *ptr; ptr++)
        if (*ptr == DELIMITER)
        {
            *ptr = '\0';
            dtinsert(container, ptr+1);
        }
}

/*===========================================================================*/
/* This is the main routine of the 'setpath' builtin. It processes the       */
/* arguments, sets up containers and builts the result on the stack.         */
/*===========================================================================*/

int b_setpath(int argc, char *argv[], void *context)
{
    Shell_t  *shp = (Shell_t*)context;
    Namval_t *np;
    Dtdisc_t disc = {0, 0, -1};
    Dt_t     *dt_old, *dt_del;
    char     ch, action = '\0', *obj, *vname = NULL;
    int      flag_e = 0, flag_u = 0, output = 0, stktop;

    /*-----------------------*/
    /* process the arguments */
    /*-----------------------*/

    while (ch = optget(argv, optget_opts))
        switch (ch)
        {
            /*-------------------*/
            /* action to perform */
            /*-------------------*/

            case 'a':
            case 'p':
            case 'x':
                if (action)
                    error(ERROR_ERROR, err_mutual, action, ch);
                else
                    action = ch;
                break;

            /*--------------------------*/
            /* what variable to process */
            /*--------------------------*/

            case 'v':
                if (vname)
                    error(ERROR_WARNING, "option -v can only be used "
                          "once, ignoring -v %s", opt_info.arg);
                else
                    vname = opt_info.arg;
                break;

            /*-------------*/
            /* other flags */
            /*-------------*/

            case 'e':
                flag_e = 1;
                break;

            case 'u':
                flag_u = 1;
                break;

            /*----------------------*/
            /* error and usage info */
            /*----------------------*/

            case ':':
                error(2, "%s", opt_info.arg);
                break;

            case '?':
                error(ERROR_usage(2), "%s", opt_info.arg);
                break;
        }

    /*-----------------------------*/
    /* finish processing arguments */
    /*-----------------------------*/

    argv += opt_info.index;
    argc -= opt_info.index;

    if (error_info.errors) error(ERROR_usage(2), "%s", usage);
    if (argc == 0 && flag_e == 0 && flag_u == 0) return 0;
    if (!vname) vname = DEFAULTVAR;

    /*---------------------------------------------------------------------*/
    /* create containers and get current content of the processed variable */
    /*---------------------------------------------------------------------*/

    dt_old = dtopen (&disc, Dtqueue);
    dt_del = dtopen (&disc, Dtset);

    np = nv_open(vname, shp->var_tree, NV_VARNAME);

    if (!dt_old || !dt_del || !np) error(ERROR_exit(1), err_internal);

    /*-------------------------------------------------------------*/
    /* fill containers with arguments and current variable content */
    /*-------------------------------------------------------------*/

    if (action == 'p')
        while (argc-- > 0) add_to_container(dt_old, *argv++);

    add_to_container (dt_old, (char *) nv_getval(np));

    if (action == 'a')
        while (argc-- > 0) add_to_container(dt_old, *argv++);

    if (action == 'x')
        while (argc-- > 0) add_to_container(dt_del, *argv++);

    /*---------------------------------------------------------------*/
    /* now copy entries from 'dt_old' to the stack for output        */
    /* - skip elements in 'dt_del' (options '-x' and '-u')           */
    /* - check for existence if option '-e' is set                   */
    /* - insert into 'dt_del' if option '-u' is set                  */
    /* - if all tests passed copy to the stack to build the new path */
    /*---------------------------------------------------------------*/

    stktop = stktell(stkstd);

    for (obj = dtfirst(dt_old); obj; obj = dtnext(dt_old, obj))
    {
        if (dtmatch(dt_del, obj)) continue;
        if (flag_e && *obj && *obj != '.' && access(obj, F_OK) < 0) continue;
        if (flag_u) dtinsert(dt_del, obj);
        if (output) sfputc(stkstd, DELIMITER);
        sfprintf(stkstd, "%s", obj);
        output = 1;
    }

    /*--------------------------------------------------*/
    /* copy the result string to the processed variable */
    /*--------------------------------------------------*/

    sfputc(stkstd, '\0');
    nv_putval(np, stkptr(stkstd, stktop), 0);
    nv_close(np);
    dtclose(dt_old);
    dtclose(dt_del);
    return 0;
}

1 Like

This might help you ....

Extracted from a blog post KSH93 Custom Builtins II I wrote a couple of years ago:

Example 5 Using name-value routines to access ksh93 internals

The purpose of this custom builtin is to set the value of a specified variable (argument 1) to the size of the specified file (argument 2).

$ statsize myfilesize /usr/bin/ksh
$ print ${myfilesize}
1157295
$

This example uses the libast nval(3) name-value routines to access the internals of ksh93 and set up a variable with the specifed name (i.e. myfilesize) and value of 1157295 as returned by stat(2) for the file /usr/bin/ksh. See the nval(3) documentation for more detailed information.

#include <shell.h>
#include <nval.h>

#define SH_DICT "statsize"

static const char usage_statsize[] =
   [-?\n@(#)$Id: stat 2008-05-03 $\n]"
   [-author?Finnbarr P. Murphy <fpmATiisc.com>]"
   [-licence?http://www.opensource.org/licenses/cpl1.0.txt]"
   [+NAME?statsize - assign size of file to variable]"
   [+DESCRIPTION?\bstat\b assigns the size of the specified file"
      "(in bytes) to the specified variable.]"
   "[+OPTIONS?none.]"
   "\n"
   "[+EXIT STATUS?] {"
      "[+0?Success.]"
      "[+>0?An error occurred.]"
   "[+SEE ALSO?\bstat\b(2)]"
;

int
b_statsize(int argc, char *argv[], void *extra)
{
   Namval_t *nvp = (Namval_t *)NULL;
   Shell_t *shp = (Shell_t *)NULL;
   struct stat st;
   long d;
   register int n;

   while (n = optget(argv, usage_statsize)) switch(n) {
      case ':':
        error(2, "%s", opt_info.arg);
        break;
      case '?':
        errormsg(SH_DICT, ERROR_usage(2), "%s",
           opt_info.arg);
        break;
   }
   argc -= opt_info.index;
   argv += opt_info.index;

   if (argc != 2)
      errormsg(SH_DICT, ERROR_usage(2), "%s",
         optusage((char*)0));

   /* get current shell context */
   shp = sh_getinterp();

   /* retrieve information about file */
   stat(argv[1], &st);
   /* assign size of file to long */
   d = (long) st.st_size;

   /* access the variables tree and add specified variable */
   nvp = nv_open(argv[0], shp->var_tree,
          NV_NOARRAY|NV_VARNAME|NV_NOASSIGN);
   if (!nv_isnull(nvp))
      nv_unset(nvp);
   nv_putval(nvp, (char *)&d, NV_INTEGER|NV_RDONLY);
   nv_close(nvp);

   return(0);
}

This custom builtin can easily be extended to provide much more information about a file using command line options. I will leave it as an exercise for you the reader.

Example 6 Print out detailed information about a specified shell variable.

$ showvar HOME
Value: /home/fpm, Flags: 12288 NV_EXPORT NV_IMPORT
$ integer i=123
$ showvar i
Value: 123, Flags: 10 NV_UINT64 NV_UTOL

See the libast header <nval.h> for detailed information about the different flags which can be associated with each variable. Note that some flags are overloaded so that they mean different things according to how they are OR�ed with other flags.

#include <shell.h>
#include <nval.h>

#define SH_DICT "showvar"

static const char usage_showvar[] =
   "[-?\n@(#)$Id: showvar 2008-05-04 $\n]"
   "[-author?Finnbarr P. Murphy <fpmATiisc.com>]"
   "[-licence?http://www.opensource.org/licenses/cpl1.0.txt]"
   "[+NAME?showvar - display variable details]"
   "[+DESCRIPTION?\bshowvar\b displays details about the
                   specified variable.]"
   "[+OPTIONS?none.]"
   "\n"
   "\nvariable_name\n"
   "\n"
   "[+EXIT STATUS?] {"
        "[+0?Success.]"
        "[+>0?An error occurred.]"
   "}"
   "[+SEE ALSO?\bstat\b(2)]"
;

struct Flag {
   int flag;
   char *name;
};

/* Note: not a complete list of all possible flags */
struct Flag Flags[] = {
   NV_ARRAY,   "NV_ARRAY",
   NV_BINARY,  "NV_BINARY",
   NV_EXPORT,  "NV_EXPORT",
   NV_HOST,    "NV_HOST",
   NV_IMPORT,  "NV_IMPORT",
   NV_LJUST,   "NV_LJUST",
   NV_LTOU,    "NV_LTOU",
   NV_RAW,     "NV_RAW",
   NV_RDONLY,  "NV_RDONLY",
   NV_REF,     "NV_REF",
   NV_RJUST,   "NV_RJUST",
   NV_TABLE,   "NV_TABLE",
   NV_TAGGED,  "NV_TAGGED",
   NV_UTOL,    "NV_UTOL",
   NV_ZFILL,   "NV_ZFILL",
   0,(char *)NULL
};

struct Flag IntFlags[] = {
   NV_LTOU|NV_UTOL|NV_INTEGER,   "NV_UINT64",
   NV_LTOU|NV_RJUST|NV_INTEGER,  "NV_UINT16",
   NV_RJUST|NV_ZFILL|NV_INTEGER, "NV_FLOAT",
   NV_UTOL|NV_ZFILL|NV_INTEGER,  "NV_LDOUBLE",
   NV_RJUST|NV_INTEGER,          "NV_INT16",
   NV_LTOU|NV_INTEGER,           "NV_UINT32",
   NV_UTOL|NV_INTEGER,           "NV_INT64",
   NV_RJUST,                     "NV_SHORT",
   NV_UTOL,                      "NV_LONG",
   NV_LTOU,                      "NV_UNSIGN",
   NV_ZFILL,                     "NV_DOUBLE",
   NV_LJUST,                     "NV_EXPNOTE",
   NV_INTEGER,                   "NV_INT32(NV_INTEGER)",
   0,(char *)NULL
};

int
b_showvar(int argc, char *argv[], void *extra)
{
    Shell_t *shp = (Shell_t *)NULL;
    Namval_t *nvp = (Namval_t *)NULL;
    char *ptr = (char *)NULL;
    int i;

    while (i = optget(argv, usage_showvar)) switch(i) {
         case ':':
           error(2, "%s", opt_info.arg);
           break;
         case '?':
           errormsg(SH_DICT, ERROR_usage(2), "%s", opt_info.arg);
           break;
    }
    argc -= opt_info.index;
    argv += opt_info.index;

    if (argc != 1)
        errormsg(SH_DICT, ERROR_usage(2), "%s", optusage((char*)0));

    /* get current shell context */
    shp = sh_getinterp();

    if ((nvp = nv_search(*argv, shp->var_tree, 0)) == NULL) {
           errormsg(SH_DICT, ERROR_exit(1),
              "%s: variable not found", *argv);
        return(1);
    }

    if ((ptr = nv_getval(nvp)) == NULL) {
        errormsg(SH_DICT, ERROR_exit(3),
                "%s: variable is NULL", *argv);
        return(1);
    }

    sfprintf(sfstdout,
             "Value: %s, Flags: %d", ptr, (int)nvp->nvflag);
    if ((int)nvp->nvflag & NV_INTEGER) {
       for (i=0; IntFlags.name != NULL; i++) {
          if ((int)nvp->nvflag & IntFlags.flag) {
              sfprintf(sfstdout, " %s", IntFlags.name);
              break;
          }
       }
    }
    for (i=0; Flags.name != NULL; i++) {
        if ((int)nvp->nvflag & Flags.flag)
            sfprintf(sfstdout, " %s", Flags.name);
    }

    sfprintf(sfstdout,"\n");
    nv_close(nvp);

    return(0);
}
1 Like