Gdbm

GNU Gdbm , or GDBM, is a library of database functions that use extensible hashing and work similar to the standard UNIX dbm. These routines are provided to a programmer needing to create and manipulate a hashed database.

See Also

Tgdbm
Tcl bindings to GDBM
TclGdbmWrapper
"attach" a GDBM database to an array variable

Import/export

AMG: The gdbm file format is not portable across machine architectures, and it may also not be portable between versions of gdbm.

The following code is useful for converting gdbm databases between architectures and versions, or simply for viewing the contents of or assembling a database. The "dump" format used by this code is simply netstrings encoding alternating key/value pairs.

This code is written in C, so I apologize if it's not quite appropriate for this wiki, but nevertheless I post it here because it helps expose gdbm to text processing which may be done with Tcl. The structure of the code is inspired by Tcl, as well.

/* gdbmio.c */

#include <errno.h>
#include <gdbm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Error reporting macros. */
#define ERR(fmt, args...) fprintf(stderr, "%s:%d: %s: " fmt "\n",\
        fileTail(__FILE__), __LINE__, __func__, ##args)
#define ERR_POSIX(fmt, args...) ERR(fmt ": %s", ##args, strerror(errno))
#define ERR_GDBM(fmt, args...) ERR(fmt ": %s (%s)", ##args,\
        gdbm_strerror(gdbm_errno), strerror(errno))

static const char *
fileTail(const char *path)
{
    const char *result = strrchr(path, '/');
    if (result) {
        return result + 1;
    } else {
        return path;
    }
}

static void
noop()
{}

int
main(int argc, const char *const argv[])
{
    static const char *const subcommandNames[] = {"export", "import", NULL};
    enum {EXPORT, IMPORT} subcommand = EXPORT;
    GDBM_FILE database;
    FILE *stream;
    datum key, next, value;
    int i, c, eofOk;
    size_t keyCap = 0, valueCap = 0;

    /* Determine the subcommand. */
    if (argc < 2) {
        ERR("wrong # args: should be \"%s subcommand ?arg ...?\"",
                fileTail(argv[0]));
        return EXIT_FAILURE;
    }
    for (i = 0; subcommandNames[i]; ++i) {
        if (strcmp(argv[1], subcommandNames[i]) == 0) {
            subcommand = i;
            break;
        }
    }
    if (!subcommandNames[i]) {
        ERR("unknown subcommand \"%s\": must be export or import", argv[1]);
        return EXIT_FAILURE;
    }

    /* Implement the export and import commands. */
    if (subcommand == EXPORT || subcommand == IMPORT) {
        /* Confirm the argument count. */
        if (argc != 4) {
            ERR("wrong # args: should be \"%s %s database dumpfile\"",
                    fileTail(argv[0]), argv[1]);
            return EXIT_FAILURE;
        }

        /* Open the database. */
        database = gdbm_open((char *)argv[2], 0,
                subcommand == EXPORT ? GDBM_READER : GDBM_NEWDB, 0644, noop);
        if (!database) {
            ERR_GDBM("gdbm_open(\"%s\")", argv[2]);
            return EXIT_FAILURE;
        }

        /* Open the input or output file. */
        if (strcmp(argv[3], "-") != 0) {
            stream = fopen(argv[3], subcommand == EXPORT ? "wb" : "rb");
            if (!stream) {
                ERR_POSIX("fopen(\"%s\")", argv[3]);
            }
        } else if (subcommand == EXPORT) {
            stream = stdout;
        } else {
            stream = stdin;
        }

        if (subcommand == EXPORT) {
            /* Copy each key and value from the database to the output. */
            key = gdbm_firstkey(database);
            while (key.dptr) {
                value = gdbm_fetch(database, key);
                fprintf(stream, "%d:", key.dsize);
                fwrite(key.dptr, key.dsize, 1, stream);
                fprintf(stream, ",%d:", value.dsize);
                fwrite(value.dptr, value.dsize, 1, stream);
                fputc(',', stream);
                free(value.dptr);
                next = gdbm_nextkey(database, key);
                free(key.dptr);
                key = next;
            }
        } else {
            /* Copy each key and value from the input into the database. */
            key.dptr = value.dptr = NULL;
            key.dsize = value.dsize = 0;
            do {
                /* Check for end of file. */
                c = fgetc(stream);
                if (c == EOF) {
                    eofOk = 1;
                    break;
                }
                eofOk = 0;
                if (ungetc(c, stream) == EOF) {
                    break;
                }

                /* Read the key size. */
                if (fscanf(stream, "%d", &key.dsize) != 1
                 || fgetc(stream) != ':') {
                    break;
                }

                /* Reallocate the key buffer if needed. */
                if (key.dsize > (int)keyCap) {
                    keyCap = key.dsize;
                    key.dptr = realloc(key.dptr, keyCap);
                    if (!key.dptr) {
                        ERR_POSIX("realloc(%zd)", keyCap);
                        eofOk = 1;
                        break;
                    }
                }

                /* Read the key and the size of the value. */
                if ((key.dsize > 0
                  && fread(key.dptr, key.dsize, 1, stream) != 1)
                 || fgetc(stream) != ','
                 || fscanf(stream, "%d", &value.dsize) != 1
                 || fgetc(stream) != ':') {
                    break;
                }

                /* Reallocate the value buffer if needed. */
                if (value.dsize > (int)valueCap) {
                    valueCap = value.dsize;
                    value.dptr = realloc(value.dptr, valueCap);
                    if (!value.dptr) {
                        ERR_POSIX("realloc(%zd)", valueCap);
                        eofOk = 1;
                        break;
                    }
                }

                /* Read the value. */
                if ((value.dsize > 0
                  && fread(value.dptr, value.dsize, 1, stream) != 1)
                 || fgetc(stream) != ',') {
                    break;
                }

                /* Store the key/value pair into the database. */
                if (gdbm_store(database, key, value, GDBM_INSERT) != 0) {
                    ERR_GDBM("gdbm_store");
                    eofOk = 1;
                    break;
                }
            } while (!feof(stream));

            /* Report file I/O errors. */
            if (ferror(stream)) {
                ERR_POSIX("read(\"%s\")", argv[3]);
            } else if (!eofOk) {
                ERR("read(\"%s\"): format error or premature EOF", argv[3]);
            }

            /* Deallocate temporary memory. */
            free(key.dptr);
            free(value.dptr);
        }

        /* Close the input or output file. */
        if (stream != stdin && stream != stdout && fclose(stream) == EOF) {
            ERR_POSIX("fclose(\"%s\")", argv[3]);
        }

        /* Close the database. */
        gdbm_close(database);
    } else {
        /* Should never happen. */
        ERR("internal error");
        return EXIT_FAILURE;
    }

    /* Success! */
    return EXIT_SUCCESS;
}

/* vim: set sts=4 sw=4 tw=80 et ft=c: */