AOLserver

# AOLserver C Examples

$Header: /cvsroot/aolserver/aolserver.com/docs/devel/c/c-examples.html,v 1.1 2002/03/07 19:15:35 kriston Exp $

    Example 1: nshello

    The following example provides a single request function which handles
    the /helloworld URL, returning 'Hello'. This module is the simplest of
    the AOLserver example modules.

    #include "ns.h"
    /*
     * This is the simplest possible example of adding a C module
     * to the AOLserver.
     */

    /*
     * The Ns_ModuleVersion exported integer is used to verify
     * this module version when loaded.  For AOLserver 2.0,
     * 1 (one) is the only valid value for this variable.
     */
    int Ns_ModuleVersion = 1;

    static Ns_OpProc Hello;

    /*
     * The Ns_ModuleInit function is the function the AOLserver
     * will call each time the module is loaded into a
     * server.  The function is passed two parameters:
     *
     * hServer:   The server `handle' as a string. This is the
     *            short name given to the virutal server such
     *            as `server1'.
     *
     * hModule:   The module `handle' as a string. This is the
     *            short name given to the module such as `hello'
     *
     * For example, if this module is known as `hello' and loaded
     * into the `server1' server with entries similar to the following
     * in the nsd.ini file:
     *
     * [ns\servers]
     * server1=My First Server
     *
     * [ns\server1\modules]
     * hello=hello.dll    ; or hello.so on Unix platforms
     *
     * This function would be called with "server1" and "hello" as
     * its arguments.
     *
     */
    int
    Ns_ModuleInit(char *hServer, char *hModule)
    {
        Ns_RegisterRequest(hServer, "GET", "/helloworld", Hello,
                                        NULL, NULL, 0);
        return NS_OK;
    }

    static int
    Hello(Ns_OpContext context, Ns_Conn *conn)
    {
        char html[]="Hello World.";
        Ns_ConnReturnHtml(conn, 200, html, strlen(html));
        return NS_OK;
    }

     Example 2: alias

    The following example is an Url-to-file translation module which
    converts a virtual URL to a physical pathname. This module is much
    more flexible than the translation routine built into the AOLserver
    and allows you to map multiple URL prefixes to different physical
    directories. This has been an often requested feature and the alias
    module will be the default translation routine in a future release.

    /*
     * This example module implements a URL->file aliasing extension to the
     * AOLserver using the Ns_SetUrlToFileProc function from the C API. Loading
     * this module enables you to define mappings such as
     * /oldplace/foo.html=newplace/foo.html.  This module maintains compatibility
     * with the existing (default) AOLserver UrlToFile function by supporting
     * UserMapDir and PageRoot configuration variables, both of which are
     * subsumed by the expressive capabilities of this module.
     *
     * You can load and configure this module by editing your nsd.ini file as
     * follows:
     * 1) In your [ns\server\server-name\modules] section, add the
     *    following:
     *    alias=alias.dll     ;or alias.so on Unix platforms
     * 2) Add a section for the alias module that contains your aliases. E.g.:
     *    [ns\server\server-name\module\alias]
     *    /oldplace/foo.html=newplace/foo.html
     *    ...
     *
     * Note that you can accomplish the above with the setup interface by using the
     * "List AOLserver Configuration Sections"  available from the "Setup Home"
     * page in expert mode, or you can edit the nsd.ini file with your favorite
     * text editor.
     *
     * When defining maps with key/value pairs, keep the following in mind:
     * 1) The key (`/oldplace/foo.html' above) is in "URI space" and the value
     *    (`newplace/foo.html' above) is in "file space".
     * 2) By default, the file space value (`newplace/foo.html' above) is assumed
     *    to be relative to the page root defined for the "server-name"
     *       server. In "file space" (right side of =), you can also
     *    specify an absolute pathname such as /home/user/html/index.html.
     * So, be careful not to use a leading `/'
     *    on the right side of the `=' unless you mean it.
     * 3) In "file space", `~username' expands to the user's default directory, and
     *    `~' looks for a user name in the second element of the URI, substituting
     *    the user's home directory appropriately.
     * 4) In "URI space", a leading `/~' mapped to a leading `~' in file spacesupports
     *    the common ~user usage in URI space.
     *
     * Now, let's clear things up with an example.  Note that the comments following each
     * mapping show a sample URI input followed by sample output generated by this module.
     *    [ns\server\server-name\module\alias]
     *    /user1=~godzilla  ;/user1 -> /home/godzilla
     *    /user2=~/pages    ;/user2/bambi -> /home/bambi/pages
     *                      ;OR /user2/bambi/foo -> /home/bambi/pages/foo
     *    /user3=~          ;/user3/bambi -> /home/bambi
     *                      ;OR /user3/bambi/html -> /home/bambi/html
     *    /~=~/pages        ;/~foot -> /home/foot/pages
     *                      ;OR /~foot/section -> /home/foot/pages/section
     *
     * Note that the last entry obviates the need for the `UserMapDir' configuration
      *  parameter. To provide compatibility with the default Ns_UrlToFile function, we'll
     * insert a mapping  to reflect the UserMapDir if it is set in the nsd.ini file.
      * If UserMapDir is defined and there is a mapping of the form: /~=~/otherplace,
      * the mapping takes precedence.
     *
     * Another mapping, more pervasive than the previous examples, is the following:
     * /=/home/newroot
     * This overrides the PageRoot parameter defined in the server-specific section
      * of nsd.ini. As you would expect, it also defines the implicit root forall other
     * relative  "file space" (right side of `=') values that appear in other aliases.
     *
     */

    #include "ns.h"
    #include
    #include
    #define MAPMETHOD "GET" /* placeholder in the general-purpose URL-space routines */
    /* The following string is appended to user directories (for compatibility) */
    #define CONFIG_USERMAPDIR  "UserMapDir"
    #define MAX_USERNAME 16

    /*
     * This data will be made available to our custom UrlToFile function
     * (AliasedUrlToFile)
     */
    typedef struct {
        char       *fromUri;
        char       *toUri;
    }           UriMap;

    static Ns_UrlToFileProc AliasedUrlToFile;
    static void            UriMapFree(UriMap * map);
    static int      TildeReplace(char *hServer, Ns_DString * dsOut, char *in, char *uri);
    static UriMap  *NewMap(char *from, char *to);

    /*
     * The Ns_ModuleVersion exported integer is used to verify this module
     * version when loaded.  For AOLserver 2.0, 1 (one) is the only valid value
     * for this variable.
     */
    int   Ns_ModuleVersion = 1;

    /*
     * The following are global IDs used for server-specific storage and
     * retrieval of data
     */
    static int      idAliases = -1;

    /*
     * The Ns_ModuleInit function is the function the AOLserver will call each
     * time the module is loaded into a server.  The function is passed
     * two parameters:
     *
     * hServer:   The server `handle' as a string. This is the short name given to
     * the virutal server such as `server1'.
     *
     * hModule:   The module `handle' as a string. This is the short name given to
     * the module such as `alias'
     *
     * For example, if this module is known as `alias' and loaded into the `server1'
     * server with entries similar to the following in the nsd.ini file:
     *
     * [ns\servers]
     * server1=My First Server
     *
     * [ns\server1\modules]
     * alias=alias.dll
     *
     * This function would be called with "server1" and "alias" as its arguments.
     *
     */
    int
    Ns_ModuleInit(char *hServer, char *hModule)
    {
        char       *moduleConfigPath;
        char       *serverConfigPath;
        char       *userMapDir;
        Ns_Set     *aliases;
        int         i;
        if (idAliases < 0) {
            idAliases = Ns_ServerSpecificAlloc();
        }
        serverConfigPath = Ns_ConfigGetPath(hServer, NULL, NULL);

        /*
              * for compatibility with the default UrlToFile, create entry forUserMapDir
              * if it is defined
              */
        if ((userMapDir = Ns_ConfigGetValue(serverConfigPath, CONFIG_USERMAPDIR)) !=
                  NULL) {
            UriMap *map;
            Ns_DString dsFilePattern;
            Ns_DStringInit(&dsFilePattern);
            Ns_DStringVarAppend(&dsFilePattern, "~/", userMapDir, NULL);
            map = NewMap("/~", Ns_DStringExport(&dsFilePattern));
            Ns_Log(Notice, "Ns_ModuleInit(%s,%s): Mapping %s to %s", hServer, hModule,
                             map->fromUri, map->toUri);
            Ns_UrlSpecificSet(hServer, MAPMETHOD, map->fromUri, idAliases,
                              map, 0, (void (*) (void *)) UriMapFree);
        }
        if ((moduleConfigPath = Ns_ConfigGetPath(hServer, hModule, NULL)) == NULL) {
            Ns_Log(Warning,
                             "Ns_ModuleInit(%s,%s): No file aliases section found in config file",
                                hServer, hModule);
        } else {
            if ((aliases = Ns_ConfigGetSection(moduleConfigPath)) != NULL) {
                if (Ns_SetSize(aliases) > 0) {
                    Ns_DString      dsNormalizedKey;
                    Ns_DStringInit(&dsNormalizedKey);
                    /*
                     * The following function causes the
                     * AOLserver to call our AliasedUrlToFile
                     * function instead of its default when
                     * mapping a URI to a filename.
                     */
                    Ns_SetUrlToFileProc(hServer, AliasedUrlToFile);
                    for (i = 0; i < Ns_SetSize(aliases); ++i) {
                        UriMap     *map;
                        char *value;
                        /*
                         * Normalize the path so that we can
                         * use it for matching in
                         * AliasedUrlToFile
                         */
                        Ns_NormalizePath(&dsNormalizedKey, Ns_SetKey(aliases, i));
                        value = Ns_SetValue(aliases, i);
                        if ((strncmp(dsNormalizedKey.string, "/~",2)==0) &&
                                                    (value[0] != `~')) {
                                      Ns_Log(Warning,
                                            "AliasedUrlToFile(%s): %s->%s ignored, %s must start with `~'",
                                hServer, dsNormalizedKey.string, value, value);
                        } else {
                                                    map = NewMap(ns_strdup(dsNormalizedKey.string),
                                                                          ns_strdup(value));
                            Ns_Log(Notice, "Ns_ModuleInit(%s,%s): Mapping %s to %s",
                                                                              hServer, hModule,  map->fromUri, map->toUri);
                                                    Ns_UrlSpecificSet(hServer,MAPMETHOD, map->fromUri,
                                                              idAliases,  map,0, (void (*) (void *)) UriMapFree);
                        }
                        Ns_DStringTrunc(&dsNormalizedKey, 0);
                    }
                    Ns_DStringFree(&dsNormalizedKey);
                }
            }
        }
        return NS_OK;
    }

    /*
     * This function, registered above via Ns_SetUrlToFileProc, will be calledby
     * the AOLserver when it maps a URL to a file.
     */
    static int
    AliasedUrlToFile(Ns_DString * dest, char *hServer, char *relpath)
    {
        int         retval = NS_OK;
        Ns_DString      dsAliasedPath;
        UriMap     *map;

        assert(relpath != NULL);
        assert(dest != NULL);
        Ns_DStringInit(&dsAliasedPath);

        /* special handling of `/~' pattern in URI */
        if (relpath[0]=='/' && relpath[1]=='~') {
            if ((map = Ns_UrlSpecificGet(hServer, MAPMETHOD, "/~", idAliases))== NULL) {
                Ns_Log(Error, "AliasedUrlToFile(%s): URI %s has not been aliased",
                            hServer, relpath);
                retval = NS_ERROR;
            } else if (map->toUri[0]=='~') {
                retval = TildeReplace(hServer, &dsAliasedPath, map->toUri, relpath);
            } else {
                Ns_Log(Bug,
                                      "AliasedUrlToFile(%s): In mapping %s->%s, %s must contain a `~'",
                            hServer, map->fromUri, map->toUri, map->toUri);
                retval = NS_ERROR;
            }
        } else {
            /*
             * Check whether the URI (relpath) has been registered for
             * aliasing...
             */
            if ((map = Ns_UrlSpecificGet(hServer, MAPMETHOD, relpath, idAliases))
                            == NULL) {
                /* no match, copy relpath to destination */
                Ns_DStringAppend(&dsAliasedPath, relpath);
            } else {
                /* matched, check for `~', exact match or initial subpath match of file
                        pattern*/
                int         relpathLen, fromUriLen;
                if (map->toUri[0] == `~') {
                    retval = TildeReplace(hServer, &dsAliasedPath, map->toUri,relpath);
                } else {
                    fromUriLen = strlen(map->fromUri);
                    relpathLen = strlen(relpath);
                    if (fromUriLen == relpathLen) {
                        /* exact match, use simple substitution */
                        Ns_DStringAppend(&dsAliasedPath, map->toUri);
                    } else if (fromUriLen < relpathLen) {
                        /*
                        * initial subpath match, substitute only for length
                        * of map->fromUri
                        */
                        Ns_DStringVarAppend(&dsAliasedPath, map->toUri,
                                relpath[fromUriLen]=='/' ? "" : "/",
                                &relpath[fromUriLen], NULL);
                    } else {
                        Ns_Log(Error, "AliasedUrlToFile(%s): %s to %s mapping is not valid",
                            hServer, map->fromUri, relpath);
                        retval = NS_ERROR;
                    }
                }
            }
        }
        if (retval == NS_OK) {
            if (Ns_PathIsAbsolute(dsAliasedPath.string) && (map != NULL)) {
                Ns_MakePath(dest, dsAliasedPath.string, NULL);
            } else {  /* relative path, use pageroot or / from URI space */
                UriMap *map;
                char *root;
                /* URI `/' takes precedence over pageroot */
                if ((map = Ns_UrlSpecificGet(hServer, MAPMETHOD, "/", idAliases))
                                    == NULL) {
                    root = Ns_PageRoot(hServer);
                } else {
                    root = map->toUri;
                }
                Ns_MakePath(dest, root, dsAliasedPath.string, NULL);
            }
        }
        Ns_DStringFree(&dsAliasedPath);
        return retval;
    }

    /*
     * This function performs the expansion and substitution related to
     * the `~' character as it appears in the URI and file patterns
     * defined in the nsd.ini file, and in the URI input.  In this function,
     * remember that `filePattern' is the string that appeared on the right
     * side of the `=' in the nsd.ini file and `uri' is the URI from our current
     * request, not to be confused with the URI (left of `=') defined in the
     * nsd.ini file which is used for pattern matching.
     *
     */

    static int
    TildeReplace(char *hServer, Ns_DString * dsOut, char *filePattern, char *uri)
    {
        int         retval = NS_OK;
        char       *pathRemainder;
        char       *nameEnd;
        char       *uriTail = `\0';
        int         found;
        char        user[MAX_USERNAME+1];
        int         len;

        assert (filePattern[0] == `~');
        if (filePattern[1] == `\0' || filePattern[1] == `/') { /*~ or ~/  in filePattern */
            char *nameStart;
            /* parse out user name, second element in uri OR string following /~ */
            if (strncmp(uri, "/~", 2) == 0) {  /* special case mapping of /~* = ~   */
                nameStart = &uri[2];
                if (*nameStart == `\0') {
                    nameStart = NULL;
                }
            } else {
                            /* skip past first element */
                if ((nameStart = strchr(&uri[1], `/')) != NULL) {
                    ++nameStart;
                }
            }
            if (nameStart == NULL) {
                Ns_Log(Error,
                                "AliasedUrlToFile:TildeReplace(%s): user name not found in URI: %s",
                               hServer, uri);
                retval = NS_ERROR;
            } else {
                uriTail = strchr(nameStart, `/');
                len = (uriTail==NULL) ? strlen(nameStart) : uriTail - nameStart;
                if (len >= sizeof(user)) {
                    Ns_Log(Warning,
                                            "AliasedUrlToFile:TildeReplace(%s): URI User name too long",
                                            hServer);
                    retval = NS_ERROR;
                } else {
                    strncpy(user, nameStart, len);
                    user[len] = `\0';
                    found = Ns_GetUserHome(dsOut, user);
                    pathRemainder = (filePattern[1]=='\0') ? "" : &filePattern[1];
                }
            }
        } else {
            uriTail = strchr(&uri[1], `/');   /* skip past substituted field in URI */
            if ((nameEnd = strchr(&filePattern[1], `/')) == NULL) {   /*  ~user */
                found = Ns_GetUserHome(dsOut, &filePattern[1]);
                pathRemainder = "";
            } else {             /* ~user/more/stuff */
                len = nameEnd - filePattern - 1;
                if (len >= sizeof(user)) {
                    Ns_Log(Warning,
                                          "AliasedUrlToFile:TildeReplace(%s): Mapped User name too long",
                                                        hServer);
                    retval = NS_ERROR;
                } else {
                    strncpy(user, &filePattern[1], len);
                    user[len] = `\0';
                    found = Ns_GetUserHome(dsOut, user);
                    pathRemainder = nameEnd;
                }
            }
        }
        if (retval == NS_OK) {
            if (!found) {
                Ns_Log(Warning, "AliasedUrlToFile:TildeReplace(%s): Unknown user",
                                                      hServer);
                retval = NS_ERROR;
            } else {
                Ns_DStringAppend(dsOut, pathRemainder);
                if (uriTail != NULL) {
                    Ns_DStringAppend(dsOut, uriTail);
                }
            }
        }
        return retval;
    }

    static UriMap *
    NewMap(char *from, char *to)
    {
        UriMap *map;
        map = ns_malloc(sizeof(UriMap));
        map->fromUri = from;
        map->toUri = to;
        return map;
    }

    static void
    UriMapFree(UriMap * map)
    {
        ns_free(map->fromUri);
        ns_free(map->toUri);
        ns_free(map);
    }

     Example 3: counter

    The following example implements a request function that generates the
    popular odometer-like page count image. The code is based on the
    popular odometer.c CGI program available at many archive sites, but
    because it's loaded directly into the AOLserver it generates the
    odometer at lightening speed, easily 10x faster!

    /*-
     *
     * This code implements an odometer-like page counter extension to the
     * AOLserver.  The code is based on the popular "odometer" CGI
     * by Chris Stephens, stephenc@pcmail.cbil.vcu.edu, and
     * Fred Christiansen, fredch@fc.hp.com (see below).
     *
     * Why use this module instead of the CGI?  By loading the code directly
     * into the AOLserver, you avoid all the painful overhead of CGI.
     * The result is this module is more than 10 times as fast as the CGI!
     * Plus, it compiles and run on all platforms!
     *
     * To use the counter, load the module and insert an image
     * link of the form .
     * The NsCounter() function will return a small odometer image of the
     * number of hits to the corresponding page (i.e., /my/page.htm).  The
     * page count is kept in a file prefixed by .odo (i.e., /my/.odo.page.htm).
     *
     * To load this module, add the following to the [ns\server\\modules]
     * section of your nsd.ini file where  is the name of the
     * server you want to add the counter too:
     *
     * counter=counter.dll    ; or counter.so on Unix platforms
     *
     * This prefix(s) of the count URL can be set with the "UrlPrefix"
     * key in the module's nsd.ini configuration file section (i.e.,
     * [ns\server\\module\counter]).  The value is a comma separated
     * list of URL prefixes.  For example, if the list includes the
     * "/cnt,/count" prefixes, then any URL which begins with /cnt or
     * /count will be processed by the NsCounter() function.
     *
     * Note: the setup interface in AOLserver 2.0 is open-ended, so you can
     * use it to view/edit these parameters by using the "List AOLserver
     * Configuration Sections"  available from the "Setup Home" page in expert
     * mode
     *
     */

    #include "ns.h"

    #include
    #include
    #include
    #ifdef WIN32
    #include
    #include
    #else
    #include
    #endif

    #define CONFIG_PREFIX       "UrlPrefix"
    #define DEFAULT_PREFIX      "/cgi-bin/counter,/NS/Counter,/ns/counter"
    #define COUNT_PREFIX        ".odo"

    static Ns_OpProc    NsCounter;

    int   Ns_ModuleVersion = 1;

    int
    Ns_ModuleInit(char *hServer, char *hModule)
    {
        char           *configPath;
        char       *urlPrefix;
        char       *next;
        Ns_DString          dsBuf;
        Ns_DString          dsPath;

        /*
         * Fetch the UrlPrefix from the config file.
         */
        configPath = Ns_ConfigGetPath(hServer, hModule, NULL);
        urlPrefix = Ns_ConfigGetValue(configPath, CONFIG_PREFIX);
        if (urlPrefix == NULL) {
        urlPrefix = DEFAULT_PREFIX;
        }

        /*
         * Register NsCounter at each of the URL's specified
         * in the comma seperated UrlPrefix list.
         */
        Ns_DStringInit(&dsBuf);
        Ns_DStringInit(&dsPath);
        Ns_DStringAppend(&dsBuf, urlPrefix);
        next = dsBuf.string;

            do {
        urlPrefix = next;
        next = strchr(urlPrefix, `,');
        if (next != NULL) {
                *next++ = `\0';
        }
        if (*urlPrefix != `/') {
            Ns_Log(Warning, "%s(%s):  Invalid URL prefix skipped:  %s",
                                                                        hModule, hServer, urlPrefix);
        } else {
            Ns_NormalizePath(&dsPath, urlPrefix);
            urlPrefix = Ns_DStringExport(&dsPath);
                    Ns_RegisterRequest(hServer, "GET", urlPrefix, NsCounter, ns_free,
                                                                        urlPrefix, 0);
        }
        } while (next != NULL);
            Ns_DStringFree(&dsBuf);
        Ns_DStringFree(&dsPath);

        return NS_OK;
    }

    /*-
    ** Originally count.c, by Chris Stephens, stephenc@pcmail.cbil.vcu.edu, (c)1995.
    ** Code cleaned up and enhanced by Fred Christiansen, fredch@fc.hp.com,
    ** as odometer.c; supports:
    ** - 8 digits instead of 7 (and by changing ODO_DIGITS, up to 10 (number
    **      of digits in an unsigned long))
    */

    #define NL              `\n'                /* new line char */
    #define ODO_DIGITS      8           /* # digits displayed */
    #define BUFSIZE         (ODO_DIGITS+2)      /* 8 digits + NL + nul */
    #define BM_HT           16          /* bitmap height */
    #define BM_WD           8           /* bitmap width */

    static char           *bitmap[] = {
    "0xff", "0xff", "0xff", "0xc3", "0x99", "0x99", "0x99", "0x99",     /* rows 1-8  of 0 */
    "0x99", "0x99", "0x99", "0x99", "0xc3", "0xff", "0xff", "0xff",     /* rows 9-16 of 0 */
    "0xff", "0xff", "0xff", "0xcf", "0xc7", "0xcf", "0xcf", "0xcf",     /* rows 1-8  of 1 */
    "0xcf", "0xcf", "0xcf", "0xcf", "0xcf", "0xff", "0xff", "0xff",     /* rows 9-16 of 1 */
    "0xff", "0xff", "0xff", "0xc3", "0x99", "0x9f", "0x9f", "0xcf",     /* rows 1-8  of 2 */
    "0xe7", "0xf3", "0xf9", "0xf9", "0x81", "0xff", "0xff", "0xff",     /* rows 9-16 of 2 */
    "0xff", "0xff", "0xff", "0xc3", "0x99", "0x9f", "0x9f", "0xc7",     /* rows 1-8  of 3 */
    "0x9f", "0x9f", "0x9f", "0x99", "0xc3", "0xff", "0xff", "0xff",     /* rows 9-16 of 3 */
    "0xff", "0xff", "0xff", "0xcf", "0xcf", "0xc7", "0xc7", "0xcb",     /* rows 1-8  of 4 */
    "0xcb", "0xcd", "0x81", "0xcf", "0x87", "0xff", "0xff", "0xff",     /* rows 9-16 of 4 */
    "0xff", "0xff", "0xff", "0x81", "0xf9", "0xf9", "0xf9", "0xc1",     /* rows 1-8  of 5 */
    "0x9f", "0x9f", "0x9f", "0x99", "0xc3", "0xff", "0xff", "0xff",     /* rows 9-16 of 5 */
    "0xff", "0xff", "0xff", "0xc7", "0xf3", "0xf9", "0xf9", "0xc1",     /* rows 1-8  of 6 */
    "0x99", "0x99", "0x99", "0x99", "0xc3", "0xff", "0xff", "0xff",     /* rows 9-16 of 6 */
    "0xff", "0xff", "0xff", "0x81", "0x99", "0x9f", "0x9f", "0xcf",     /* rows 1-8  of 7 */
    "0xcf", "0xe7", "0xe7", "0xf3", "0xf3", "0xff", "0xff", "0xff",     /* rows 9-16 of 7 */
    "0xff", "0xff", "0xff", "0xc3", "0x99", "0x99", "0x99", "0xc3",     /* rows 1-8  of 8 */
    "0x99", "0x99", "0x99", "0x99", "0xc3", "0xff", "0xff", "0xff",     /* rows 9-16 of 8 */
    "0xff", "0xff", "0xff", "0xc3", "0x99", "0x99", "0x99", "0x99",     /* rows 1-8  of 9 */
    "0x83", "0x9f", "0x9f", "0xcf", "0xe3", "0xff", "0xff", "0xff"      /* rows 9-16 of 9 */
    };

    /*
     * NsCounter -
     *
     * Handle a request of the form "/NS/Counter/my/page.htm" to create an
     * odometer-like count of visits to the /my/page.htm page.
     *
     */
    static int
    NsCounter(void *context, Ns_Conn *conn)
    {
        char            buf[BUFSIZE], dial[ODO_DIGITS];
        unsigned long   cnt;
        int             status, i, l;
        char           *p;
        Ns_DString      ds;
        int                 fd;
        char           *uri;
        char       *hServer;
        char       *urlPrefix;
        int                 nPrefix;

        hServer = Ns_ConnServer(conn);
        urlPrefix = (char *) context;

        /*
         * Figure out how many parts are in the
         * prefix, i.e., /NS/Counter has 2 parts.
         */
        nPrefix = 0;
        p = urlPrefix;
        while ((p = strchr(p, `/')) != NULL) {
        ++p;
        ++nPrefix;
        }
        if (conn->request->urlc > nPrefix) {
        uri = Ns_SkipUrl(conn->request, nPrefix);
        } else {
        return Ns_ConnReturnBadRequest(conn, "Missing URI");
        }

        /*
         * Convert the URI to the file, sneaking in counter prefix before the last
         * path element.
         */
        Ns_DStringInit(&ds);
        p = strrchr(uri, `/');
        *p = `\0';
        Ns_UrlToFile(&ds, hServer, uri);
        Ns_DStringAppend(&ds, "/");
        Ns_DStringAppend(&ds, COUNT_PREFIX);
        *p++ = `/';
        Ns_DStringAppend(&ds, ".");
        Ns_DStringAppend(&ds, p);

        /*
         * Open (or create) the counter file and increment the page count.
         */
        fd = open(ds.string, O_RDWR | O_CREAT, 0660);
        if (fd < 0) {
        Ns_Log(Error, "Could not open counter file `%s' for URI `%s':  %s",
            ds.string, uri, strerror(errno));
                Ns_DStringFree(&ds);
        return Ns_ConnReturnInternalError(conn);
        }
        Ns_DStringFree(&ds);
        i = read(fd, buf, BUFSIZE);
        if (i > 0) {
        buf[i] = `\0';
        cnt = strtoul(buf, (char **) NULL, 10);
        } else {
        cnt = 0;
        }
        lseek(fd, 0, SEEK_SET);
        sprintf(buf, "%u\n", ++cnt);
        write(fd, buf, strlen(buf));
        close(fd);

        /* Calc length (ignoring NL) and copy right-to-left into dial */

        for (l = 0, p = buf; *p != `\0' && *p != NL; l++, p++);
        for (i = 0, p = buf; i < l; i++, p++)
        dial[ODO_DIGITS - l + i] = *p;
        for (i = 0; i < (ODO_DIGITS - l); i++)  /* backfill with zeros */
        dial[i] = `0';

        /*
         * Build up the odometer xbm.
         */
        Ns_DStringPrintf(&ds, "#define odo_width  %d\n", (BM_WD * ODO_DIGITS));
        Ns_DStringPrintf(&ds, "#define odo_height %d\n", BM_HT);
        Ns_DStringAppend(&ds, "static char odo_bits[] = {\n");
        for (i = 0; i < BM_HT; i++) {
        for (l = 0; l < ODO_DIGITS; l++) {
            Ns_DStringAppend(&ds, bitmap[(((dial[l] - `0') * BM_HT) + i)]);
            Ns_DStringAppend(&ds, ", ");
            if (l == 7)
                Ns_DStringAppend(&ds, "\n");
        }
        if (i == 15) {
            Ns_DStringAppend(&ds, "};");
        }
        }
        Ns_DStringAppend(&ds, "\n");

        /*
         * Setup the HTTP headers.
         */
        Ns_ConnSetRequiredHeaders(conn, "image/x-xbitmap", ds.length);
        status = Ns_ConnFlushHeaders(conn, 200);
        if (status == NS_OK) {
        /*
         * Send the bitmap if the headers were sent o.k.
         *
         * Note that the underlying communications driver may send
         * less than the number of bytes requested so we need to
         * call Ns_ConnWrite() in a while loop to ensure all the
         * bytes are sent.  If the connection is shut down while
         * attempting to send, the communication driver returns
         * -1 and we set status to NS_ERROR and immediately break
         * out of the loop.  Whenever a status other than NS_OK
         * is returned from a request function, the AOLserver does
         * not run the registered trace functions (trace functions
         * are normally used for access logging and you shouldn't
         * normally log aborted connections).
         *
         * Actually, in this trivial case, you could simply use:
         *
         *      status = Ns_ConnPuts(conn, ds.string)
         *
         * which pretty much implements the loop below.
         */
        p = ds.string;
        i = ds.length;
        while (i > 0) {
                cnt = Ns_ConnWrite(conn, p, i);
                if (cnt < 0) {
                        status = NS_ERROR;
                        break;
                }
                i -= cnt;
                p += cnt;
        }
        }
        Ns_DStringFree(&ds);
        return status;
    }

     Example 4: stats

    The following example provides several cooperating facilities to
    implement a simple connection statistics system. The module includes a
    trace to collect the statistics on each connection, a scheduled
    procedure to aggregate the statistics, the /NS/Stat request function
    to print the statistics, and a Tcl command to access the statistics in
    a script.

    /*
     * stat.c - Example C module.
     *
     * This example incorporates many of the features of the AOLserver
     * C API in a single simple, but useful, statistics gathering module.
     * The module maitains statistics on the number of connections and
     * bytes sent by a server.  The data is stored in a simple
     * `StatContext' structure which is allocated when the module is
     * loaded.  The StatContext stores the current, last, and total numbers
     * and is updated after each connection by the `StatTrace' connection
     * trace procedure.  At a regular interval (the interval in seconds
     * is configurable), the `StatUpdate' funcation is called to log the
     * current and total number to the AOLserver log file and then
     * makes the current data the last data.  Access to these data is
     * provided in two forms:
     *
     * 1.  A simple C request function, `StatRequest', will return the
     * current data as a simple notice page.  The StatRequest function
     * is registered at the GET /NS/Stat URL of the server.
     *
     * 2.  The `ns_stat' Tcl command is added to each Tcl interpreter
     * of the server interpreter pool.  This allows a Tcl script
     * to access the current, last, or total data at any time.
     *
     * Because the AOLserver is completely multithreaded, the statistics
     * data can be updated and queried simultaneously.  An Ns_Mutex is
     * included in the ServerContext structure to keep multiple threads
     * from accessing the data at the same time.
     *
     * Finally, on shutdown, the `StatShutdown' function is called to
     * print out a final tally and clean up the ServerContext structure.
     *
     * Note that all the features of this module are specific to the
     * server it is loaded into.  The server handle, `hServer',
     * is used to makes sure the trace, request, Tcl command, and
     * shutdown functions are all run in the context of the single
     * server.  Other virtaul servers may ignore or load the
     * stat module as well and the AOLserver guarantees the data
     * will be keep separate for each server.
    */

    #include "ns.h"
    #include "nstcl.h"

    /*
     * The default statistics update interval is 1 minute or 60 seconds.
     */
    #define DEFAULT_INTERVAL    60

    /*
     * Definitions of the StatContext structure which is allocated
     * on a per-server basis.
     */
    typedef struct {
        int     bytes;
        int conns;
    } StatData;

    typedef struct {
        Ns_Mutex lock;
        int interval;
        char *hServer;
        StatData current;
        StatData last;
        StatData total;
    } StatContext;

    /*
     * Forward declarations of functions which will be referenced
     * in the stat module initialization function.
     */
    static Tcl_CmdProc StatCmd;
    static Ns_TclInterpInitProc StatTclInit;
    static Ns_TraceProc StatTrace;
    static Ns_Callback StatUpdate;
    static Ns_Callback StatShutdown;
    static Ns_OpProc StatRequest;

    /*
     * The Ns_ModuleVersion exported integer is used to verify
     * this module version when loaded.  For AOLserver 2.0,
     * 1 (one) is the only valid value for this variable.
     */
    int Ns_ModuleVersion = 1;

    /*
     * The Ns_ModuleInit function is the function the AOLserver
     * will call each time the module is loaded into a
     * server.  The function is passed two parameters:
     *
     * hServer:   The server `handle' as a string. This is the
     *            short name given to the virutal server such
     *            as `server1'.
     *
     * hModule:   The module `handle' as a string. This is the
     *            short name given to the module such as `stat'
     *
     * For example, if this module is known as `stat' and loaded
     * into the `server1' server with entries similar to the following
     * in the nsd.ini file:
     *
     * [ns\servers]
     * server1=My First Server
     *
     * [ns\server1\modules]
     * stat=stat.dll
     *
     * This function would be called with "server1" and "stat" as
     * its arguments.
     *
     */
    int
    Ns_ModuleInit(char *hServer, char *hModule)
    {
        char *configPath;
        StatContext *ctx;

        /*
         * Create and initalize the statistics context.
         */
        ctx = ns_malloc(sizeof(StatContext));
        Ns_InitializeMutex(&ctx->lock);
        ctx->hServer = hServer;
        ctx->current.bytes = 0;
        ctx->current.conns = 0;
        ctx->last.bytes = 0;
        ctx->last.conns = 0;
        ctx->total.bytes = 0;
        ctx->total.conns = 0;

        /*
         * Determine the statistics interval from the config file.
         * The Ns_ConfigGetPath function will expand to the
         * `server module specific' configuration section, e.g.,
         * [ns\server1\module\stat].
         */
        configPath = Ns_ConfigGetPath(hServer, hModule, NULL);
        if (!Ns_ConfigGetInt(configPath, "Interval", &ctx->interval)) {
                ctx->interval = DEFAULT_INTERVAL;
        }

        /*
         * Register the trace to accumulate the statistics.
         */
        Ns_RegisterServerTrace(hServer, StatTrace, ctx);

        /*
         * Register the statistics update function to run at
         * regular intervals.
         */
        Ns_ScheduleProc(StatUpdate, ctx, 0, ctx->interval);

        /*
         * Add the GET /NS/Stat request function.
         */
        Ns_RegisterRequest(hServer, "GET", "/NS/Stat", StatRequest,
                                                                    NULL, ctx,0);

        /*
         * Add the Tcl "ns_stat" command to the interpreter pool.
         * Note how the context is passed to StatTclInit which
         * then passes it to the Tcl_CreateCommand function.
         */
        Ns_TclInitInterps(hServer, StatTclInit, ctx);

        /*
         * Register the statistics shutdown procedure which
         * cleans up the context on server shutdown.
         */
        Ns_RegisterServerShutdown(hServer, StatShutdown, ctx);

        return NS_OK;
    }

    /*
     * StatTrace is called after each connection and accumulates the
     * statistics.
     */
    static void
    StatTrace(void *ctx, Ns_Conn *conn)
    {
        StatContext *sc;
        int bytes;

        sc = (StatContext *) ctx;
        Ns_LockMutex(&sc->lock);
        bytes = Ns_ConnResponseLength(conn);
        sc->current.bytes += bytes;
        sc->total.bytes += bytes;
        ++sc->current.conns;
        ++sc->total.conns;
        Ns_UnlockMutex(&sc->lock);
    }

    /*
     * StatUpdate aggregates the statistics and logs the data to
     * the AOLserver server log.
     */
    static void
    StatUpdate(void *ctx)
    {
        StatContext *sc;

        sc = (StatContext *) ctx;

        Ns_LockMutex(&sc->lock);
        Ns_Log(Notice,
                            "StatUpdate(%s):  Last: conns %d, bytes %d  Total:conns %d, bytes %d",
                sc->hServer, sc->current.conns, sc->current.bytes,
                sc->total.conns, sc->total.bytes);
        sc->last.conns = sc->current.conns;
        sc->last.bytes = sc->current.bytes;
        sc->current.conns = 0;
        sc->current.bytes = 0;
        Ns_UnlockMutex(&sc->lock);
    }

    /*
     * StatTclInit is called once for each Tcl interpreter
     * in the virutal server's Tcl interpreter pool.
     */
    static int
    StatTclInit(Tcl_Interp *interp, void *ctx)
    {
        Tcl_CreateCommand(interp, "ns_stat", StatCmd, ctx, NULL);
        return NS_OK;
    }

    /*
     * StatCmd implements the Tcl "ns_stat" command.
     */
    static int
    StatCmd(ClientData ctx, Tcl_Interp *interp, int argc, char **argv)
    {
        StatContext *sc;
        StatData *sd;

        sc = (StatContext *) ctx;
        if (argc != 2) {
                Tcl_AppendResult(interp, "wrong # args: should be \"",
                        argv[0], " command\"", NULL);
                return TCL_ERROR;
        }
        if (strcmp(argv[1], "current") == 0) {
                sd = &sc->current;
        } else if (strcmp(argv[1], "last") == 0) {
                sd = &sc->last;
        } else if (strcmp(argv[1], "total") == 0) {
                sd = &sc->total;
        } else {
                Tcl_AppendResult(interp, "unknown command \"",
                        argv[1], "\":  should be current, last, or total", NULL);
                return TCL_ERROR;
        }

        Ns_LockMutex(&sc->lock);
        sprintf(interp->result, "%d %d", sd->conns, sd->bytes);
        Ns_UnlockMutex(&sc->lock);
        return TCL_OK;
    }

    /*
     * StatRequest is a simple AOLserver request function which returns
     * the current data in a simple HTML page.  The page is generated
     * with the Ns_ConnReturnNotice() function which is used throughout the
     * AOLserver to generate simple HTML page responses with the
     * AOLserver banner logo.  Ns_ConnReturnNotice() takes a string as
     * the HTML page content.  We use an Ns_DString to quickly build
     * up the string - Ns_DString's grow as needed so we don't have
     * to worry about buffer overflow.  The HTML page is a simple
     * HTML3  which formats the current, last, and total
     * statistics for the server.
     */
    static int
    StatRequest(void *ctx, Ns_Conn *conn)
    {
        StatContext *sc;
        Ns_DString ds;
        int retcode;

        sc = (StatContext *) ctx;
        Ns_DStringInit(&ds);
        /*
         * Build up the HTML3  with the latest data.
         */
        Ns_DStringAppend(&ds, "");
        Ns_DStringVarAppend(&ds, "", sc->hServer, "", NULL);
        Ns_DStringAppend(&ds,
                                                  "CurrentLastTotal");
        Ns_LockMutex(&sc->lock);
        Ns_DStringPrintf(&ds,
                                  "# connections%d%d%d",
                sc->current.conns, sc->last.conns, sc->total.conns);
        Ns_DStringPrintf(&ds,
                                            "# bytes%d%d%d",
                          sc->current.bytes, sc->last.bytes, sc->total.bytes);
        Ns_UnlockMutex(&sc->lock);
        Ns_DStringAppend(&ds, "");
        /*
         * Return the HTML page using Ns_ConnReturnNotice().
         */
        retcode = Ns_ConnReturnNotice(conn, 200, "Server Statistics",
       ds.string);
        /*
         * Don't forget to free the dstring!
         */
        Ns_DStringFree(&ds);

        return retcode;
    }

    /*
     * StatShutdown simple prints a final statistics entry to the
     * server log and then cleans up the StatContext structure.
     */
    static void
    StatShutdown(void *ctx)
    {
        StatContext *sc;

        sc = (StatContext *) ctx;
        Ns_Log(Notice, "StatShutdown(%s):  Total:  conns %d, bytes %d.",
                sc->hServer, sc->total.conns, sc->total.bytes);
        Ns_DestroyMutex(&sc->lock);
        ns_free(ctx);
    }

     Example 5: tclhello

    The following example illustrates how to extend the Tcl scripting
    language built into the AOLserver with your own custom C code.

    #include "nstcl.h"

    /*
     * This code adds a new TCL command named "hello" to the AOLserver.
     * To test this module, add the following to the
     * [ns\server\server-name\modules] section of your nsd.ini file:
     * tclhello=tclhello.dll   ; or tclhello.so on Unix platforms
     *
     * You can then run your "hello" command from the /NS/EvalTcl
     * interface provided by the AOLserver.
     */

    int Ns_ModuleVersion = 1;
    static Tcl_CmdProc HelloCmd;

    static int
    HelloCmd(ClientData dummy, Tcl_Interp *interp, int argc, char **argv)
    {
        interp->result = "Hello";
        return TCL_OK;
    }

    static int
    HelloInterpInit(Tcl_Interp *interp, void *context)
    {
        Tcl_CreateCommand(interp, "hello", HelloCmd, NULL, NULL);
        return NS_OK;
    }

    int
    Ns_ModuleInit(char *hServer, char *hModule)
    {
        return (Ns_TclInitInterps(hServer, HelloInterpInit, NULL));
    }

     Example 6: tclcs

    The following example adds critical section enter and leave commands
    to Tcl. This allows you to protect shared resources (i.e., an external
    file or remote connection) accessed in a Tcl script.

    #include "ns.h"
    #include "nstcl.h"

    /*
     * This code implements simple critical section primitives as TCL commands
     * within the AOLserver.  To use this module, add the following to the
     * [ns\server\server-name\modules] section of your nsd.ini file:
     * cs=tclcs.dll    ; or tclcs.so on Unix platforms
     *
     * Within your TCL, these commands should be used as follows:
     *      ...
     *      cs_enter
     *      set err [catch {some tcl}]
     *      cs_leave
     *      if $err {
     *              error $err
     *      }
     *      ...
     * Note that any error should be caught to avoid leaving the
     * critical section in the locked state after the script exits.
     *
     * Also, because the init function uses the module name to construct
     * the command names,  you can load the module multiple times if you
     * need more than one critical section.  For example, if you needed 2
     * critical sections, your nsd.ini would have:
     *
     * [ns\server\server-name\modules]
     * cs1=tclcs.dll
     * cs2=tclcs.dll
     *
     * and then, in your Tcl script, you would access the two critical
     * sections using their unique names:
     *
     *  ...
     *  cs1_enter
     *  ... access resource protected by cs 1 ...
     *  cs2_enter
     *  ... access resource 2, leaving resource 1 locked ...
     *  cs2_leave
     *  cs1_leave
     *
     */

    int Ns_ModuleVersion = 1;

    static int InitCs(Tcl_Interp *interp, void *ctx);
    static Tcl_CmdProc EnterCs, LeaveCs;

    /*
     * This structure is used to pass the critical section
     * and enter and leave command names to InitCs function
     * through the Ns_TclInitInterps function.
     */
    typedef struct {
        char *enter;
        char *leave;
        Ns_CriticalSection *cs;
    } CsCtx;

    /*
     * Ns_ModuleInit - The function is called each time the
     * tclcs module is loaded into a server.  It
     * constructs the names of the enter and leave Tcl commands
     * from the module name and then calls Ns_TclInitInterps
     * to add the command to each interpreter of the
     * server.
     */
    int
    Ns_ModuleInit(char *hServer, char *hModule)
    {
        Ns_DString dsEnter;
        Ns_DString dsLeave;
        CsCtx ctx;
        int status;

        Ns_DStringInit(&dsEnter);
        Ns_DStringAppend(&dsEnter, hModule);
        Ns_DStringAppend(&dsEnter, "_enter");

        Ns_DStringInit(&dsLeave);
        Ns_DStringAppend(&dsLeave, hModule);
        Ns_DStringAppend(&dsLeave, "_leave");

        ctx.enter = dsEnter.string;
        ctx.leave = dsLeave.string;
            ctx.cs = ns_malloc(sizeof(Ns_CriticalSection));
            Ns_InitializeCriticalSection(ctx.cs);

            status = Ns_TclInitInterps(hServer, InitCs, (void *) &ctx);

        Ns_DStringFree(&dsEnter);
        Ns_DStringFree(&dsLeave);
        return status;
    }

    /*
     * InitCs - Initialize a single Tcl interpreter with the
     * critical section enter and leave commands.
     */
    static int
    InitCs(Tcl_Interp *interp, void *ctx)
    {
        CsCtx *csctx;

        csctx = ctx;
            Tcl_CreateCommand(interp, csctx->enter, EnterCs,
                                                        (ClientData) csctx->cs, NULL);
            Tcl_CreateCommand(interp, csctx->leave, LeaveCs,
                                                        (ClientData) csctx->cs, NULL);
            return TCL_OK;
    }

    /*
     * EnterCs - Enter the critical section passed in as callback data.
     */
    static int
    EnterCs(ClientData clientData, Tcl_Interp *interp, int argc, char **argv)
    {
            Ns_CriticalSection *cs = (Ns_CriticalSection *) clientData;

            Ns_EnterCriticalSection(cs);
            return TCL_OK;
    }

    /*
     * LeaveCs - Leave the critical section passed in as callback data.
     */
    static int
    LeaveCs(ClientData clientData, Tcl_Interp *interp, int argc, char **argv)
    {
            Ns_CriticalSection *cs = (Ns_CriticalSection *) clientData;

            Ns_LeaveCriticalSection(cs);
            return TCL_OK;
    }

     Example 7: postgres

    The following example shows how to create a database driver. This
    `postgres' module is a database services driver which allows the
    AOLserver to use the Postgres95 database.

    #include "ns.h"
    #include "nsdb.h"
    #include "nstcl.h"
    #include "libpq-fe.h"
    #include
    #include

    /*-

    What is this?
    ------------

    This module implements a simple AOLserver database services driver.  A
    database driver is a module which interfaces between the AOLserver
    database-independent nsdb module and the API of a particular DBMS.  A
    database driver's job is to open connections, send SQL statements, and
    translate the results into the form used by nsdb.  In this case, the
    driver is for the Postgres95 DBMS from UC Berekely.  Postgres95 can be
    downloaded and installed on most Unix systems.  To use this driver, you
    must have Postgres95 installed on your system.  For more information on
    Postgres95 or to download the code, open:

            http://s2k-ftp.cs.berkeley.edu:8000/postgres95

    How does it work?
    ----------------

    Driver modules look much like ordinary AOLserver modules but are
    loaded differently.  Instead of being listed with other modules in the
    [ns\server\\modules] configuration section, a database
    driver is listed in the [ns\module\nsdb\drivers] section and nsdb does
    the loading.  The database driver initialization function normally does
    little more than call the nsdb Ns_DbRegisterDriver() function with an
    array of pointers to functions.  The functions are then later used by
    nsdb to open database connections and send and process queries.
    In addition to open,
    select, and getrow functions, the driver also provides system catalog
    functions and a function for initializing a virtual server.  The
    virtual server initialization function is called each time nsdb is
    loaded into a virtual server.  In this case, the server initialization
    function, Ns_PgServerInit, adds the "ns_pg" Tcl command to the server's
    Tcl interpreters which can be used to fetch information about an active
    Postgres95 connection in a Tcl script.

    */

    #define DRIVER_NAME             "Postgres95"

    static char    *Ns_PgName(Ns_DbHandle *handle);
    static int      Ns_PgOpenDb(Ns_DbHandle *dbhandle);
    static int      Ns_PgCloseDb(Ns_DbHandle *dbhandle);
    static int      Ns_PgCmd(Ns_DbHandle *handle, char *sql);
    static int      Ns_PgReconnect(Ns_DbHandle *handle);
    static Ns_Set  *Ns_PgSelect(Ns_DbHandle *handle, char *sql);
    static int      Ns_PgGetRow(Ns_DbHandle *handle, Ns_Set *row);
    static int      Ns_PgFlush(Ns_DbHandle *handle);
    static Ns_DbTableInfo *Ns_PgGetTableInfo(Ns_DbHandle *handle, char *table);
    static char    *Ns_PgTableList(Ns_DString *pds, Ns_DbHandle *handle,
                                                                  int includesystem);
    static char    *Ns_PgBestRowId(Ns_DString *pds, Ns_DbHandle *handle, char *table);
    static int       Ns_PgServerInit(char *hServer, char *hModule, char *hDriver);
    static char    *pgName = DRIVER_NAME;
    static unsigned int pgCNum = 0;

    /*-
     *
     * The NULL-terminated PgProcs[] array of Ns_DbProc structures is the
     * method by which the function pointers are passed to the nsdb module
     * through the Ns_DbRegisterDriver() function.  Each Ns_DbProc includes
     * the function id (i.e., DbFn_OpenDb, DbFn_CloseDb, etc.) and the
     * cooresponding driver function pointer (i.e., Ns_PgOpendb, Ns_PgCloseDb,
     * etc.).  See nsdb.h for a complete list of function ids.
     */
    static Ns_DbProc PgProcs[] = {
        {DbFn_Name, (void *) Ns_PgName},
        {DbFn_OpenDb, (void *) Ns_PgOpenDb},
        {DbFn_CloseDb, (void *) Ns_PgCloseDb},
        {DbFn_DML, (void *) Ns_PgCmd},
        {DbFn_Select, (void *) Ns_PgSelect},
        {DbFn_GetRow, (void *) Ns_PgGetRow},
        {DbFn_Flush, (void *) Ns_PgFlush},
        {DbFn_Cancel, (void *) Ns_PgFlush},
        {DbFn_GetTableInfo, (void *) Ns_PgGetTableInfo},
        {DbFn_TableList, (void *) Ns_PgTableList},
        {DbFn_BestRowId, (void *) Ns_PgBestRowId},
        {DbFn_ServerInit, (void *) Ns_PgServerInit},
        {0, NULL}
    };

    /*
     * The NsPgConn structure is connection data specific
     * to Postgres95.
     */
    typedef struct NsPgConn {
        PGconn         *conn;
        unsigned int    cNum;
        PGresult       *res;
        int             nCols;
        int             nTuples;
        int             curTuple;
    }               NsPgConn;

    DllExport int   Ns_ModuleVersion = 1;

    DllExport int
    Ns_DbDriverInit(char *hDriver, char *configPath)
    {
        /*
         * Register the Postgres95 driver functions with nsdb.
         * Nsdb will later call the Ns_PgServerInit() function
         * for each virtual server which utilizes nsdb.
         */
        if (Ns_DbRegisterDriver(hDriver, &(PgProcs[0])) != NS_OK) {
            Ns_Log(Error, "Ns_DbDriverInit(%s):  Could not register the %s driver.",
                hDriver, pgName);
            return NS_ERROR;
        }
        Ns_Log(Notice, "%s loaded.", pgName);
        return NS_OK;
    }

    /*
     * Ns_PgName - Return the string name which identifies the Postgres95 driver.
     */
    static char    *
    Ns_PgName(Ns_DbHandle *ignored)
    {
        return pgName;
    }

    /*
     * Ns_PgOpenDb - Open an Postgres95 connection on an nsdb handle. The
     * datasource for Postgres95 is in the form "host:port:database".
     */
    static int
    Ns_PgOpenDb(Ns_DbHandle *handle)
    {
        NsPgConn       *nsConn;
        PGconn         *pgConn;
        char           *host;
        char           *port;
        char           *db;
        int             status;

        assert(handle != NULL);

        status = NS_ERROR;
        host = handle->datasource;
        port = strchr(handle->datasource, `:');
        if (port == NULL || ((db = strchr(port + 1, `:')) == NULL)) {
            Ns_Log(Error, "Ns_PgOpenDb(%s):  Malformed datasource:  %s",
            handle->driver, handle->datasource);
        } else {
            *port++ = `\0';
            *db++ = `\0';
            Ns_Log(Notice, "Opening %s on %s, %s", db, host, port);
            pgConn = PQsetdb(host, port, NULL, NULL, db);
            *--db = `:';
            *--port = `:';
            if (PQstatus(pgConn) == CONNECTION_OK) {
                Ns_Log(Notice, "Ns_PgOpenDb(%s):  Openned connection to %s.",
                handle->driver, handle->datasource);
                nsConn = ns_malloc(sizeof(NsPgConn));
            nsConn->cNum = pgCNum++;
                nsConn->conn = pgConn;
                nsConn->res = NULL;
                nsConn->nCols = nsConn->nTuples = nsConn->curTuple = 0;
                handle->connection = nsConn;
                status = NS_OK;
            } else {
                Ns_Log(Error, "Ns_PgOpenDb(%s):  Could not connect to %s:  %s",
                                    handle->driver, handle->datasource, PQerrorMessage(pgConn));
                PQfinish(pgConn);
            return NS_ERROR;
            }
        }
        return NS_OK;
    }

    /*
     * Ns_PgCloseDb - Close an Postgres95 connection on an nsdb handle.
     */
    static int
    Ns_PgCloseDb(Ns_DbHandle *handle)
    {
        NsPgConn       *nsConn;

        assert(handle != NULL);

        nsConn = handle->connection;
        if (handle->verbose) {
        Ns_Log(Notice, "Ns_PgCloseDb(%d):  Closing connection:  %s",
            nsConn->cNum, handle->datasource);
        }
        PQfinish(nsConn->conn);
        nsConn->conn = NULL;
        nsConn->nCols = nsConn->nTuples = nsConn->curTuple = 0;
        ns_free(nsConn);
        handle->connection = NULL;
        return NS_OK;
    }

    /*
     * Ns_PgExec - Send a Postgres95 query.  This function does not
     * implement an nsdb function but is used internally by the Postgres95
     * driver.
     */
    static int
    Ns_PgExec(Ns_DbHandle *handle, char *sql)
    {
        NsPgConn       *nsConn;
        Ns_DString      dsSql;
        int             status;

        assert(handle != NULL);
        assert(sql != NULL);

        status = NS_ERROR;
        nsConn = handle->connection;
        Ns_DStringInit(&dsSql);
        Ns_DStringAppend(&dsSql, sql);
        while (dsSql.length > 0 && isspace(dsSql.string[dsSql.length - 1])) {
            dsSql.string[--dsSql.length] = `\0';
        }
        if (dsSql.length > 0 && dsSql.string[dsSql.length - 1] != `;') {
            Ns_DStringNAppend(&dsSql, ";", 1);
        }
        nsConn->res = PQexec(nsConn->conn, dsSql.string);
        Ns_DStringFree(&dsSql);
        if (nsConn->res == NULL) {
            Ns_Log(Error, "Ns_PgExec(%s):  Could not send query `%s':  %s",
                handle->datasource, sql, PQerrorMessage(nsConn->conn));
            return NS_ERROR;
        }
        return NS_OK;
    }

    /*
     * Ns_PgCmd - Send a query which should return a command result.
     */
    static int
    Ns_PgCmd(Ns_DbHandle *handle, char *sql)
    {
        int             status;
        NsPgConn       *nsConn;

        assert(handle != NULL);
        assert(sql != NULL);

        nsConn = handle->connection;
        status = Ns_PgExec(handle, sql);
        if (status == NS_OK) {
            if (PQresultStatus(nsConn->res) != PGRES_COMMAND_OK) {
                Ns_Log(Error,
                                    "Ns_PgCmd(%s):  Query `%s' did not return PGRES_COMMAND_OK status.",
                    handle->datasource, sql);
                status = NS_ERROR;
            }
        nsConn->nCols = 0;
            PQclear(nsConn->res);
        nsConn->res = NULL;
        }
        return status;
    }

    /*
     * Ns_PgSelect - Send a query which should return rows.
     */
    static Ns_Set  *
    Ns_PgSelect(Ns_DbHandle *handle, char *sql)
    {
        Ns_Set         *row;
        NsPgConn       *nsConn;
        int             i;

        assert(handle != NULL);
        assert(sql != NULL);

        row = NULL;
        nsConn = handle->connection;
        if (Ns_PgExec(handle, sql) == NS_OK) {
        if (PQresultStatus(nsConn->res) == PGRES_TUPLES_OK) {
                nsConn->curTuple = 0;
                nsConn->nCols = PQnfields(nsConn->res);
                nsConn->nTuples = PQntuples(nsConn->res);
            row = handle->row;
                for (i = 0; i < nsConn->nCols; ++i) {
                    Ns_SetPut(row, PQfname(nsConn->res, i), NULL);
                }
            } else {
            Ns_Log(Error, "Ns_PgSelect(%s):  Query did not return rows:  %s",
                handle->datasource, sql);
        }
        }
        return row;
    }

    /*
     * Ns_PgGetRow - Fetch rows after an Ns_PgSelect.
     */
    static int
    Ns_PgGetRow(Ns_DbHandle *handle, Ns_Set *row)
    {
        NsPgConn       *nsConn;
        int             status;
        int             i;

        assert(handle != NULL);
        assert(row != NULL);
        assert(handle->connection != NULL);

        nsConn = handle->connection;
        if (nsConn->nCols == 0) {
            Ns_Log(Error, "Ns_PgGetRow(%s):  Get row called outside a fetch row loop.",
            handle->datasource);
            status = NS_ERROR;
        } else if (nsConn->curTuple == nsConn->nTuples) {
            PQclear(nsConn->res);
        nsConn->res = NULL;
            nsConn->nCols = nsConn->nTuples = nsConn->curTuple = 0;
            status = NS_END_DATA;
        } else {
            for (i = 0; i < nsConn->nCols; i++) {
                Ns_SetPutValue(row, (int) i, PQgetvalue(nsConn->res,
                                                          nsConn->curTuple, i));
            }
            ++nsConn->curTuple;
            return NS_OK;
        }
        return status;
    }

    /*
     * Ns_PgFlush - Flush any waiting rows not needed after an Ns_DbSelect().
     */
    static int
    Ns_PgFlush(Ns_DbHandle *handle)
    {
        NsPgConn       *nsConn;

        assert(handle != NULL);
        assert(handle->connection != NULL);

        nsConn = handle->connection;
        if (nsConn->nCols > 0) {
            PQclear(nsConn->res);
        nsConn->res = NULL;
            nsConn->nCols = nsConn->nTuples = nsConn->curTuple = 0;
        }
        return NS_OK;
    }

    /*
     * Ns_DbTableInfo - Return system catalog information (columns, types, etc.)
     * about a table.
     */
    static Ns_DbTableInfo *
    Ns_PgGetTableInfo(Ns_DbHandle *handle, char *table)
    {
        Ns_Set         *row;
        Ns_Set         *col;
        Ns_DString      ds;
        Ns_DbTableInfo *tinfo;
        int             status;
        char           *name;
        char           *type;

        Ns_DStringInit(&ds);
        Ns_DStringVarAppend(&ds, "SELECT a.attname, t.typname "
            "FROM pg_class c, pg_attribute a, pg_type t "
            "WHERE c.relname = `", table, "` "
            "and a.attnum > 0 and a.attrelid = c.oid "
            "and a.atttypid = t.oid ORDER BY attname", NULL);

        row = Ns_DbSelect(handle, ds.string);
        Ns_DStringFree(&ds);
        tinfo = NULL;
        if (row != NULL) {
            while ((status = Ns_PgGetRow(handle, row)) == NS_OK) {
                name = row->fields[0].value;
                type = row->fields[1].value;
                if (name == NULL || type == NULL) {
                    Ns_Log(Error,
                              "Ns_PgGetTableInfo(%s):  Invalid `pg_attribute' entry for table:  %s",
                        handle->datasource, table);
                    break;
                }

                /*
                 * NB:  Move the fields directly from the row
             * Ns_Set to the col Ns_Set to avoid a data copy.
                 */
                col = Ns_SetCreate(NULL);
                col->name = name;
                Ns_SetPut(col, "type", NULL);
                col->fields[0].value = type;
                row->fields[0].value = NULL;
                row->fields[1].value = NULL;
                if (tinfo == NULL) {
                    tinfo = Ns_DbNewTableInfo(table);
                }
                Ns_DbAddColumnInfo(tinfo, col);
            }
            if (status != NS_END_DATA && tinfo != NULL) {
                Ns_DbFreeTableInfo(tinfo);
                tinfo = NULL;
            }
        }
        return tinfo;
    }

    /*
     * Ns_PgTableList - Return a list of tables in the database.
     */
    static char    *
    Ns_PgTableList(Ns_DString *pds, Ns_DbHandle *handle, int fSystemTables)
    {
        Ns_Set         *row;
        Ns_DString      ds;
        char           *table;
        int             status;

        Ns_DStringInit(&ds);
        Ns_DStringAppend(&ds, "SELECT relname FROM pg_class "
            "WHERE relkind = `r' and relname !~ `^Inv' ");
        if (!fSystemTables) {
            Ns_DStringAppend(&ds, "and relname !~ `^pg_' ");
        }
        Ns_DStringAppend(&ds, "ORDER BY relname");
        row = Ns_DbSelect(handle, ds.string);
        Ns_DStringFree(&ds);
        status = NS_ERROR;
        if (row != NULL) {
            while ((status = Ns_DbGetRow(handle, row)) == NS_OK) {
                table = row->fields[0].value;
                if (table == NULL) {
                    Ns_Log(Warning,
                                          "Ns_PgTableList(%s):  NULL relname in `pg_class' table.",
                     handle->datasource);
                } else {
                    Ns_DStringNAppend(pds, table, strlen(table) + 1);
                }
            }
        }
        if (status == NS_END_DATA) {
            return pds->string;
        }
        return NULL;
    }

    /*
     * Ns_PgBestRowId - Return the primary key of a table.  If a table
     * has a primary key, the AOLserver can perform row updates and
     * deletes.  In the case of Postgres95, the "oid" system column is alwasy
     * unique so we just return it instead of looking for an actual
     * primary key.
     */
    static char    *
    Ns_PgBestRowId(Ns_DString *pds, Ns_DbHandle *handle, char *table)
    {
        Ns_DStringNAppend(pds, "oid", 4);
        return pds->string;
    }

    /*
     * PgCmd - This function implements the "ns_pg" Tcl command installed into
     * each interpreter of each virtual server.  It provides access to features
     * specific to the Postgres95 driver.
     */
    static int
    PgCmd(ClientData dummy, Tcl_Interp *interp, int argc, char **argv)
    {
        Ns_DbHandle    *handle;
        NsPgConn        *pgconn;

        if (argc != 3) {
            Tcl_AppendResult(interp, "wrong # args: should be \"",
                argv[0], " command dbId\"", NULL);
            return TCL_ERROR;
        }
        if (Ns_TclDbGetHandle(interp, argv[2], &handle) != TCL_OK) {
            return TCL_ERROR;
        }

        /*
         * Make sure this is a Postgres95 handle before accessing
         * handle->connection as an NsPgConn.
         */
        if (Ns_DbDriverName(handle) != pgName) {
            Tcl_AppendResult(interp, "handle \"", argv[1], "\" is not of type \"",
                pgName, "\"", NULL);
            return TCL_ERROR;
        }
        pgconn = (NsPgConn *) handle->connection;
        if (!strcmp(argv[1], "db")) {
        Tcl_SetResult(interp, PQdb(pgconn->conn), TCL_STATIC);
        } else if (!strcmp(argv[1], "host")) {
        Tcl_SetResult(interp, PQhost(pgconn->conn), TCL_STATIC);
        } else if (!strcmp(argv[1], "options")) {
        Tcl_SetResult(interp, PQoptions(pgconn->conn), TCL_STATIC);
        } else if (!strcmp(argv[1], "port")) {
        Tcl_SetResult(interp, PQport(pgconn->conn), TCL_STATIC);
        } else if (!strcmp(argv[1], "number")) {
        sprintf(interp->result, "%u", pgconn->cNum);
        } else if (!strcmp(argv[1], "error")) {
        Tcl_SetResult(interp, PQerrorMessage(pgconn->conn), TCL_STATIC);
        } else if (!strcmp(argv[1], "status")) {
        if (PQstatus(pgconn->conn) == CONNECTION_OK) {
            interp->result = "ok";
        } else {
            interp->result = "bad";
        }
        } else {
            Tcl_AppendResult(interp, "unknown command \"", argv[2],
                "\": should be db, host, options, port, error or status.", NULL);
            return TCL_ERROR;
        }
        return TCL_OK;
    }

    /*
     * Ns_PgInterpInit - Add the "ns_pg" command to a single Tcl interpreter.
     */
    static int
    Ns_PgInterpInit(Tcl_Interp *interp, void *ignored)
    {
        Tcl_CreateCommand(interp, "ns_pg", PgCmd, NULL, NULL);
        return NS_OK;
    }

    /*
     * Ns_PgServerInit - Have Ns_PgInterpInit called for each interpreter in
     * the virtual server which is being intialized.
     */
    static int
    Ns_PgServerInit(char *hServer, char *hModule, char *hDriver)
    {
        return Ns_TclInitInterps(hServer, Ns_PgInterpInit, NULL);
    }