commit 84b498bcfd9a3db0d30c554bab5c005406fc2367
Author: Sebastian Gniazdowski <sgniazdowski@gmail.com>
Date:   Sun Jun 25 08:52:45 2017 +0200

    Two modules

diff --git a/Src/Modules/db.c b/Src/Modules/db.c
new file mode 100644
index 0000000..839ae22
--- /dev/null
+++ b/Src/Modules/db.c
@@ -0,0 +1,811 @@
+/* -*- Mode: C; c-default-style: "linux"; c-basic-offset: 4; indent-tabs-mode: nil -*-
+ * vim:sw=4:sts=4:et
+ */
+
+/*
+ * db.c - general database module, forwards to backends
+ *
+ * Copyright (c) 2017 Sebastian Gniazdowski
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Sebastian Gniazdowski or the Zsh Development Group be
+ * liable to any party for direct, indirect, special, incidental, or
+ * consequential damages arising out of the use of this software and its
+ * documentation, even if Paul Falstad and the Zsh Development Group have
+ * been advised of the possibility of such damage.
+ *
+ * Sebastian Gniazdowski and the Zsh Development Group specifically disclaim
+ * any warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software provided
+ * hereunder is on an "as is" basis, and Paul Falstad and the Zsh Development
+ * Group have no obligation to provide maintenance, support, updates,
+ * enhancements, or modifications.
+ */
+
+#include "db.mdh"
+#include "db.pro"
+#include "db.h"
+
+/* MACROS {{{ */
+#ifndef PM_UPTODATE
+#define PM_UPTODATE     (1<<19) /* Parameter has up-to-date data (e.g. loaded from DB) */
+#endif
+/* }}} */
+/* DECLARATIONS {{{ */
+static void ztie_usage();
+static void zuntie_usage();
+static void ztaddress_usage();
+static void ztclear_usage();
+
+static HashTable createhashtable(char *name);
+static void freebackendnode(HashNode hn);
+static void backend_scan_fun(HashNode hn, int unused);
+
+static Param createhashparam(char *name, int flags);
+
+/* Type of provided (by backend module) entry-point */
+typedef int (*DbBackendEntryPoint)(VA_ALIST1(int cmd));
+
+struct backend_node {
+    struct hashnode node;
+    DbBackendEntryPoint main_entry;
+};
+
+typedef struct backend_node *BackendNode;
+
+/* Maps db/dbtype onto BackendNode */
+static HashTable backends_hash = NULL;
+
+/* For searching with scanhashtable */
+char *In_ParamName = NULL;
+DbBackendEntryPoint Out_FoundBe = NULL;
+/* }}} */
+/* ARRAY: builtin {{{ */
+static struct builtin bintab[] =
+{
+    /* h - help, d - backend type, r - read-only, a/f - address/file,
+     * l - load password from terminal, p - password as argument,
+     * P - password from file, z - zero read-cache */
+    BUILTIN("ztie", 0, bin_ztie, 0, -1, 0, "hrzf:d:a:p:P:L:", NULL),
+    BUILTIN("zuntie", 0, bin_zuntie, 0, -1, 0, "uh", NULL),
+    BUILTIN("ztaddress", 0, bin_ztaddress, 0, -1, 0, "h", NULL),
+    BUILTIN("ztclear", 0, bin_ztclear, 0, -1, 0, "h", NULL),
+};
+/* }}} */
+/* ARRAY: other {{{ */
+#define ROARRPARAMDEF(name, var)                                        \
+    { name, PM_ARRAY | PM_READONLY, (void *) var, NULL,  NULL, NULL, NULL }
+/* }}} */
+
+/* FUNCTION: update_user_hash {{{ */
+static void update_user_hash(char *id, int is_loading) {
+    Param pm = (Param) paramtab->getnode(paramtab, "zdb_backends");
+    HashTable ht;
+    HashNode hn;
+    Param val_pm;
+
+    if(!pm) {
+        zwarn("no such parameter: zdb_backends, internal error");
+        return;
+    }
+
+    /* Must be a special hash */
+    if (!(pm->node.flags & PM_HASHED) || !(pm->node.flags & PM_SPECIAL)) {
+        zwarn("parameter zdb_backends is defined by user, will not update it");
+        return;
+    }
+
+    ht = pm->u.hash;
+    hn = gethashnode2(ht, id);
+    val_pm = (Param) hn;
+
+    if (val_pm) {
+        if (val_pm->u.str) {
+            zsfree(val_pm->u.str);
+            val_pm->u.str = NULL;
+        }
+        if (is_loading) {
+            val_pm->u.str = ztrdup("re-loaded");
+        } else {
+            val_pm->u.str = ztrdup("unloaded");
+        }
+    } else {
+        val_pm = (Param) zshcalloc(sizeof (*val_pm));
+        val_pm->node.flags = PM_SCALAR | PM_HASHELEM;
+        val_pm->gsu.s = &stdscalar_gsu;
+        if (is_loading) {
+            val_pm->u.str = ztrdup("loaded");
+        } else {
+            val_pm->u.str = ztrdup("unloaded");
+        }
+        addhashnode(ht, ztrdup(id), val_pm); /* sets pm->node.nam */
+    }
+}
+/* }}} */
+/* FUNCTION: zsh_db_register_backend {{{ */
+
+/**/
+void
+zsh_db_register_backend(char *id, void *entry_point) {
+    BackendNode bn = (BackendNode)zshcalloc(sizeof(struct backend_node));
+    if (bn) {
+        bn->main_entry = entry_point;
+        addhashnode(backends_hash, ztrdup(id), (void *)bn);
+    } else {
+        zwarn("out of memory when allocating backend entry");
+        return;
+    }
+
+    update_user_hash(id, 1);
+}
+/* }}} */
+/* FUNCTION: zsh_db_unregister_backend {{{ */
+
+/**/
+void
+zsh_db_unregister_backend(char *id) {
+    HashNode bn = backends_hash->removenode(backends_hash, id);
+    if (bn) {
+        freebackendnode(bn);
+    }
+    update_user_hash(id, 0);
+}
+/* }}} */
+/* FUNCTION: bin_ztie {{{ */
+
+/**/
+static int
+bin_ztie(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname;
+    char *address = NULL, *pass = NULL, *pfile = NULL, *lazy = NULL;
+    int rdonly = 0, zcache = 0, pprompt = 0;
+
+    /* Check options */
+
+    if (OPT_ISSET(ops,'h')) {
+        ztie_usage();
+        return 0;
+    }
+
+    if (!OPT_ISSET(ops,'d')) {
+        zwarnnam(nam, "you must pass e.g. `-d db/redis', see `-h'");
+        return 1;
+    }
+
+    if (!OPT_ISSET(ops,'f') && !OPT_ISSET(ops,'a')) {
+        zwarnnam(nam, "you must pass `-f' (file, or `-a', address) with e.g. {host}[:port][/[db_idx][/key]], see `-h'");
+        return 1;
+    }
+
+    if (0 != strcmp(OPT_ARG(ops, 'd'), "db/redis") && 0 != strcmp(OPT_ARG(ops, 'd'), "db/gdbm") ) {
+        zwarnnam(nam, "Unsupported backend type `%s', see `-h'", OPT_ARG(ops, 'd'));
+        return 1;
+    }
+
+    /* Check argument */
+    pmname = *args;
+
+    if (!pmname) {
+        zwarnnam(nam, "You must pass non-option argument - the target parameter to create, see -h");
+        return 1;
+    }
+
+    /* Address */
+    if (OPT_ISSET(ops,'f')) {
+        address = OPT_ARG(ops,'f');
+    } else {
+        address = OPT_ARG(ops,'a');
+    }
+
+    /* Read-only */
+    if (OPT_ISSET(ops,'r')) {
+        rdonly = 1;
+    } else {
+        rdonly = 0;
+    }
+
+    /* Zero-cache */
+    if (OPT_ISSET(ops,'z')) {
+        zcache = 1;
+    } else {
+        zcache = 0;
+    }
+
+    /* Password */
+    if (OPT_ISSET(ops,'p')) {
+        pass = OPT_ARG(ops,'p');
+    }
+
+    /* Password file */
+    if (OPT_ISSET(ops,'P')) {
+        pfile = OPT_ARG(ops,'P');
+    }
+
+    /* Password load request */
+    if (OPT_ISSET(ops,'l')) {
+        pprompt = 1;
+    } else {
+        pprompt = 0;
+    }
+
+    /* Lazy binding */
+    if (OPT_ISSET(ops,'L')) {
+        lazy = OPT_ARG(ops,'L');
+    }
+
+    BackendNode node = NULL;
+    DbBackendEntryPoint be = NULL;
+
+    if(!(node = (BackendNode) gethashnode2(backends_hash, OPT_ARG(ops, 'd')))) {
+        zwarnnam(nam, "backend module for %s not loaded (or loaded before the main `db' module)", OPT_ARG(ops, 'd'));
+        return 1;
+    }
+
+    be = node->main_entry;
+    if (!be) {
+        zwarnnam(nam, "backend for %s is uninitialized", OPT_ARG(ops, 'd'));
+        return 1;
+    }
+
+    return be(DB_TIE, address, rdonly, zcache, pass, pfile, pprompt, pmname, lazy);
+}
+/* }}} */
+/* FUNCTION: bin_zuntie {{{ */
+
+/**/
+static int
+bin_zuntie(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname;
+    int ret = 0, rountie = 0;
+
+    if (OPT_ISSET(ops,'h')) {
+        zuntie_usage();
+        return 0;
+    }
+
+    if (!*args) {
+        zwarnnam(nam, "at least one variable name is needed, see -h");
+        return 1;
+    }
+
+    for (pmname = *args; *args++; pmname = *args) {
+        In_ParamName = pmname;
+        Out_FoundBe = NULL;
+
+        scanhashtable(backends_hash, 0, 0, 0, backend_scan_fun, 0);
+
+        if (!Out_FoundBe) {
+            zwarnnam(nam, "didn't recognize `%s' as a tied parameter", pmname);
+            continue;
+        }
+
+        if (OPT_ISSET(ops,'u')) {
+            rountie = 1;
+        }
+
+        ret = Out_FoundBe(DB_UNTIE, rountie, pmname) ? 1 : ret;
+    }
+
+    return ret;
+}
+/* }}} */
+/* FUNCTION: bin_ztaddress {{{ */
+
+/**/
+static int
+bin_ztaddress(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname;
+    int ret = 0;
+
+    if (OPT_ISSET(ops,'h')) {
+        ztaddress_usage();
+        return 0;
+    }
+
+    if (!*args) {
+        zwarnnam(nam, "one parameter name is needed, see -h");
+        return 1;
+    }
+
+    pmname = *args;
+    In_ParamName = pmname;
+    Out_FoundBe = NULL;
+
+    scanhashtable(backends_hash, 0, 0, 0, backend_scan_fun, 0);
+
+    if (!Out_FoundBe) {
+        zwarnnam(nam, "didn't recognize `%s' as a tied parameter", pmname);
+        return 1;
+    }
+
+    ret = Out_FoundBe(DB_GET_ADDRESS, pmname) ? 1 : ret;
+
+    return ret;
+}
+/* }}} */
+/* FUNCTION: bin_ztclear {{{ */
+
+/**/
+static int
+bin_ztclear(char *nam, char **args, Options ops, UNUSED(int func))
+{
+    char *pmname, *key;
+    int ret = 0;
+
+    if (OPT_ISSET(ops,'h')) {
+        ztclear_usage();
+        return 0;
+    }
+
+    if (!*args) {
+        zwarnnam(nam, "one-to-two parameters' names are needed, see -h");
+        return 1;
+    }
+
+    pmname = *args;
+    key = *(args+1);
+
+    In_ParamName = pmname;
+    Out_FoundBe = NULL;
+
+    scanhashtable(backends_hash, 0, 0, 0, backend_scan_fun, 0);
+
+    if (!Out_FoundBe) {
+        zwarnnam(nam, "didn't recognize `%s' as a tied parameter", pmname);
+        return 1;
+    }
+
+    ret = Out_FoundBe(DB_CLEAR_CACHE, pmname, key) ? 1 : ret;
+
+    return ret;
+}
+/* }}} */
+
+/*************** MAIN CODE ***************/
+
+/* ARRAY features {{{ */
+static struct features module_features =
+{
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+/* }}} */
+
+/* FUNCTION: setup_ {{{ */
+
+/**/
+int
+setup_(UNUSED(Module m))
+{
+    Param pm  = NULL;
+
+    /* Create private registration hash */
+    if (!(backends_hash = createhashtable("ZSH_BACKENDS"))) {
+        zwarn("cannot create backend-register hash");
+        return 1;
+    }
+
+    /* Unset zdb_backends if it exists. Inherited from db_gdbm,
+     * it clears current scope, leaving any upper scope untouched,
+     * which can result in zdb_backends being local. Can be seen
+     * as a feature */
+    if ((pm = (Param)paramtab->getnode(paramtab, "zdb_backends")) && !(pm->node.flags & PM_UNSET)) {
+        pm->node.flags &= ~PM_READONLY;
+        if (unsetparam_pm(pm, 0, 1)) {
+            zwarn("cannot properly manage scoping of variables");
+            return 1;
+        }
+    }
+
+    /* Create zdb_backends public hash that will hold state of backends */
+    pm = createhashparam("zdb_backends", PM_READONLY | PM_REMOVABLE);
+    if (NULL == pm) {
+        zwarn("cannot create user backends-list hash parameter");
+        if (backends_hash) {
+            deletehashtable(backends_hash);
+            backends_hash = NULL;
+        }
+        return 1;
+    }
+    pm->gsu.h = &stdhash_gsu;
+
+    return 0;
+}
+/* }}} */
+/* FUNCTION: features_ {{{ */
+
+/**/
+int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m, &module_features);
+    return 0;
+}
+/* }}} */
+/* FUNCTION: enables_ {{{ */
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m, &module_features, enables);
+}
+/* }}} */
+/* FUNCTION: boot_ {{{ */
+
+/**/
+int
+boot_(UNUSED(Module m))
+{
+    return 0;
+}
+/* }}} */
+/* FUNCTION: cleanup_ {{{ */
+
+/**/
+int
+cleanup_(Module m)
+{
+    /* This frees `zredis_tied` */
+    return setfeatureenables(m, &module_features, NULL);
+
+    if (backends_hash) {
+        deletehashtable(backends_hash);
+        backends_hash = NULL;
+    }
+}
+/* }}} */
+/* FUNCTION: finish_ {{{ */
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+    return 0;
+}
+/* }}} */
+
+/*************** UTILITIES ***************/
+
+/* FUNCTION: createhashtable {{{ */
+static HashTable
+createhashtable(char *name)
+{
+    HashTable ht;
+
+    ht = newhashtable(8, name, NULL);
+
+    ht->hash        = hasher;
+    ht->emptytable  = emptyhashtable;
+    ht->filltable   = NULL;
+    ht->cmpnodes    = strcmp;
+    ht->addnode     = addhashnode;
+    ht->getnode     = gethashnode2;
+    ht->getnode2    = gethashnode2;
+    ht->removenode  = removehashnode;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = freebackendnode;
+    ht->printnode   = NULL;
+
+    return ht;
+}
+/* }}} */
+/* FUNCTION: createhashparam {{{ */
+static Param
+createhashparam(char *name, int flags)
+{
+    Param pm;
+    HashTable ht;
+
+    pm = createparam(name, flags | PM_SPECIAL | PM_HASHED);
+    if (!pm) {
+        return NULL;
+    }
+
+    if (pm->old)
+        pm->level = locallevel;
+
+    /* This creates standard hash. */
+    ht = pm->u.hash = newparamtable(7, name);
+    if (!pm->u.hash) {
+        paramtab->removenode(paramtab, name);
+        paramtab->freenode(&pm->node);
+        zwarnnam(name, "out of memory when allocating user-visible hash of backends");
+        return NULL;
+    }
+
+    /* Does free Param (unsetfn is called) */
+    ht->freenode = zsh_db_freeparamnode;
+
+    return pm;
+}
+/* }}} */
+/* FUNCTION: freebackendnode {{{ */
+static void
+freebackendnode(HashNode hn)
+{
+    zsfree(hn->nam);
+    zfree(hn, sizeof(struct backend_node));
+}
+/* }}} */
+/* FUNCTION: backend_scan_fun {{{ */
+static void
+backend_scan_fun(HashNode hn, int unused)
+{
+    BackendNode bn = (BackendNode)hn;
+    DbBackendEntryPoint be = bn->main_entry;
+    if (!be) {
+        zwarn("backend %s registered but uninitialized", hn->nam);
+        return;
+    }
+    /* 0 - shell true value */
+    if(0 == be(DB_IS_TIED, In_ParamName)) {
+        Out_FoundBe = be;
+    }
+}
+/* }}} */
+
+/*********** SHARED UTILITIES ***********/
+
+/* FUNCTION: zsh_db_unmetafy_zalloc {{{ */
+
+/*
+ * Unmetafy that:
+ * - duplicates bufer to work on it,
+ * - does zalloc of exact size for the new string,
+ * - restores work buffer to original content, to restore strlen
+ *
+ * No zsfree()-confusing string will be produced.
+ */
+
+/**/
+char *
+zsh_db_unmetafy_zalloc(const char *to_copy, int *new_len)
+{
+    char *work, *to_return;
+    int my_new_len = 0;
+
+    work = ztrdup(to_copy);
+    work = unmetafy(work, &my_new_len);
+
+    if (new_len)
+        *new_len = my_new_len;
+
+    /* This string can be correctly zsfree()-d */
+    to_return = (char *) zalloc((my_new_len+1)*sizeof(char));
+    memcpy(to_return, work, sizeof(char)*my_new_len); /* memcpy handles $'\0' */
+    to_return[my_new_len]='\0';
+
+    /* Restore original strlen and correctly free */
+    strcpy(work, to_copy);
+    zsfree(work);
+
+    return to_return;
+}
+/* }}} */
+/* FUNCTION: zsh_db_set_length {{{ */
+
+/* For zsh-allocator, rest of Zsh seems to use
+ * free() instead of zsfree(), and such length
+ * restoration causes slowdown, but all is this
+ * way strict - correct */
+
+/**/
+void
+zsh_db_set_length(char *buf, int size)
+{
+    buf[size]='\0';
+    while (-- size >= 0) {
+        buf[size]=' ';
+    }
+}
+/* }}} */
+/* FUNCTION: zsh_db_standarize_hash {{{ */
+
+/**/
+void
+zsh_db_standarize_hash(Param pm) {
+    if (0 == (pm->node.flags & PM_HASHED)) {
+        return;
+    }
+
+    pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
+    pm->gsu.h = &stdhash_gsu;
+
+    HashTable ht = pm->u.hash;
+
+    ht->hash        = hasher;
+    ht->emptytable  = emptyhashtable;
+    ht->filltable   = NULL;
+    ht->cmpnodes    = strcmp;
+    ht->addnode     = addhashnode;
+    ht->getnode     = gethashnode;
+    ht->getnode2    = gethashnode2;
+    ht->removenode  = removehashnode;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = zsh_db_freeparamnode;
+}
+/* }}} */
+/* FUNCTION: zsh_db_arr_append {{{ */
+
+/*
+ * Adds parameter name (to given `*_tied` array)
+ */
+
+/**/
+int
+zsh_db_arr_append(char ***arr, const char *input_s)
+{
+    int old_len = arrlen(*arr);
+    char **new_arr = zshcalloc( (old_len+2) * sizeof(char *));
+
+    /* Copy */
+    char **p = *arr;
+    char **dst = new_arr;
+    while (*p) {
+        *dst++ = *p++;
+    }
+
+    /* Append new one */
+    *dst = ztrdup(input_s);
+
+    /* Substitute, free old one */
+    zfree(*arr, sizeof(char *) * (old_len + 1));
+    *arr = new_arr;
+
+    return 0;
+}
+/* }}} */
+/* FUNCTION: zsh_db_filter_arr {{{ */
+
+/*
+ * Removes parameter name (from given `*_tied` array)
+ */
+
+/**/
+int
+zsh_db_filter_arr(char ***arr, const char *input_s)
+{
+    int old_len = arrlen(*arr), new_len;
+    char **new_arr, **dst;
+
+    /* Two stage, to always have arrlen() == zfree-size - 1.
+     * Could do allocation and revert when `not found`, but
+     * what would be better about that. */
+
+    /* Find one to remove */
+    char **p = *arr;
+    while (*p) {
+        if (0==strcmp(input_s,*p)) {
+            break;
+        }
+        p++;
+    }
+
+    /* Copy x+1 to x */
+    while (*p) {
+        *p=*(p+1);
+        p++;
+    }
+
+    /* Second stage. Size changed? Only old_size-1
+     * change is possible, but.. paranoia way */
+    new_len = arrlen(*arr);
+    if (new_len != old_len) {
+        new_arr = (char **) zshcalloc((new_len+1) * sizeof(char *));
+
+        /* Copy */
+        p = *arr;
+        dst = new_arr;
+        while (*p) {
+            *dst++ = *p++;
+        }
+        *dst = NULL;
+
+        /* Substitute, free old one */
+        zfree(*arr, sizeof(char *) * (old_len + 1));
+        *arr = new_arr;
+    }
+
+    return 0;
+}
+/* }}} */
+/* FUNCTION: zsh_db_freeparamnode {{{ */
+
+/**/
+void
+zsh_db_freeparamnode(HashNode hn)
+{
+    Param pm = (Param) hn;
+
+    /* Upstream: The second argument of unsetfn() is used by modules to
+     * differentiate "exp"licit unset from implicit unset, as when
+     * a parameter is going out of scope.  It's not clear which
+     * of these applies here, but passing 1 has always worked.
+     */
+
+    /* if (delunset) */
+    pm->gsu.s->unsetfn(pm, 1);
+
+    zsfree(pm->node.nam);
+    /* If this variable was tied by the user, ename was ztrdup'd */
+    if (pm->node.flags & PM_TIED && pm->ename) {
+        zsfree(pm->ename);
+        pm->ename = NULL;
+    }
+    zfree(pm, sizeof(struct param));
+}
+/* }}} */
+
+/***************** USAGE *****************/
+
+/* FUNCTION: ztie_usage {{{ */
+
+static void
+ztie_usage()
+{
+    fprintf(stdout, "Usage: ztie -d db/... [-z] [-r] [-p password] [-P password_file] [-L type]"
+            "-f/-a {db_address} {parameter_name}\n");
+    fprintf(stdout, "Options:\n");
+    fprintf(stdout, " -d:       select database type: \"db/gdbm\", \"db/redis\"\n");
+    fprintf(stdout, " -z:       zero-cache for read operations (always access database)\n");
+    fprintf(stdout, " -r:       create read-only parameter\n" );
+    fprintf(stdout, " -f or -a: database-address in format {host}[:port][/[db_idx][/key]] or a file path\n");
+    fprintf(stdout, " -p:       database-password to be used for authentication\n");
+    fprintf(stdout, " -P:       path to file with database-password\n");
+    fprintf(stdout, " -L:       lazy binding - provide type of key to create if it doesn't exist "
+                    "(string, set, zset, hash, list)\n");
+    fprintf(stdout, "The {parameter_name} - choose name for the created database-bound parameter\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: zuntie_usage {{{ */
+
+static void
+zuntie_usage()
+{
+    fprintf(stdout, "Usage: zuntie [-u] {tied-variable-name} [tied-variable-name] ...\n");
+    fprintf(stdout, "Options:\n");
+    fprintf(stdout, " -u: Allow to untie read-only parameter\n");
+    fprintf(stdout, "Description: detaches variable from its database and removes the variable;\n");
+    fprintf(stdout, "             database is not cleared (unlike when unset)\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: ztaddress_usage {{{ */
+
+static void
+ztaddress_usage()
+{
+    fprintf(stdout, "Usage: ztaddress {tied-parameter-name}\n");
+    fprintf(stdout, "Description: stores address used by given parameter to $REPLY\n");
+    fflush(stdout);
+}
+/* }}} */
+/* FUNCTION: ztclear_usage {{{ */
+
+static void
+ztclear_usage()
+{
+    fprintf(stdout, "Usage: ztclear {tied-parameter-name} [key name]\n");
+    fprintf(stdout, "Description: clears cache of given hash/key or of given plain\n");
+    fprintf(stdout, "             parameter: set (array), list (array), string (scalar);\n");
+    fprintf(stdout, "             pass `-z' to ztie to globally disable cache for parameter\n");
+    fflush(stdout);
+}
+/* }}} */
+
diff --git a/Src/Modules/db.h b/Src/Modules/db.h
new file mode 100644
index 0000000..321e5b3
--- /dev/null
+++ b/Src/Modules/db.h
@@ -0,0 +1,42 @@
+/*
+ * db.h
+ *
+ * Copyright (c) 2017 Sebastian Gniazdowski
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Sebastian Gniazdowski or the Zsh Development Group be
+ * liable to any party for direct, indirect, special, incidental, or
+ * consequential damages arising out of the use of this software and its
+ * documentation, even if Paul Falstad and the Zsh Development Group have
+ * been advised of the possibility of such damage.
+ *
+ * Sebastian Gniazdowski and the Zsh Development Group specifically disclaim
+ * any warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose.  The software provided
+ * hereunder is on an "as is" basis, and Paul Falstad and the Zsh Development
+ * Group have no obligation to provide maintenance, support, updates,
+ * enhancements, or modifications.
+ */
+
+/* Backend commands */
+#define DB_TIE 1
+#define DB_UNTIE 2
+#define DB_IS_TIED 3
+#define DB_GET_ADDRESS 4
+#define DB_CLEAR_CACHE 5
+
+/* Data types, created according to redis */
+#define DB_KEY_TYPE_NONE 0  /* unknown type because non-existent key */
+#define DB_KEY_TYPE_UNKNOWN 1
+#define DB_KEY_TYPE_NO_KEY 2
+#define DB_KEY_TYPE_STRING 3
+#define DB_KEY_TYPE_LIST 4
+#define DB_KEY_TYPE_SET 5
+#define DB_KEY_TYPE_ZSET 6
+#define DB_KEY_TYPE_HASH 7
diff --git a/Src/Modules/db.mdd b/Src/Modules/db.mdd
new file mode 100644
index 0000000..73ac83d
--- /dev/null
+++ b/Src/Modules/db.mdd
@@ -0,0 +1,7 @@
+name=zsh/db
+link=dynamic
+load=no
+
+autofeatures="b:ztie b:zuntie b:ztaddress b:ztclear"
+
+objects="db.o"
diff --git a/Src/Modules/db_gdbm.c b/Src/Modules/db_gdbm.c
index 0ab0fe7..2e6d023 100644
--- a/Src/Modules/db_gdbm.c
+++ b/Src/Modules/db_gdbm.c
@@ -1,3 +1,7 @@
+/* -*- Mode: C; c-default-style: "linux"; c-basic-offset: 4; indent-tabs-mode: nil -*-
+ * vim:sw=4:sts=4:et
+ */
+
 /*
  * db_gdbm.c - bindings for gdbm
  *
@@ -15,13 +19,13 @@
  * purpose, provided that the above copyright notice and the following
  * two paragraphs appear in all copies of this software.
  *
- * In no event shall Clint Adams or the Zsh Development
+ * In no event shall Clint Adams, Sebastian Gniazdowski or the Zsh Development
  * Group be liable to any party for direct, indirect, special, incidental, or
  * consequential damages arising out of the use of this software and its
  * documentation, even if Peter Stephenson, Sven Wischnowsky and the Zsh
  * Development Group have been advised of the possibility of such damage.
  *
- * Clint Adams and the Zsh Development Group
+ * Clint Adams, Sebastian Gniazdowski, and the Zsh Development Group
  * specifically disclaim any warranties, including, but not limited to, the
  * implied warranties of merchantability and fitness for a particular purpose.
  * The software provided hereunder is on an "as is" basis, and Peter
@@ -33,16 +37,21 @@
 
 #include "db_gdbm.mdh"
 #include "db_gdbm.pro"
+#include "db.epro"
+#include "db.h"
 
+/* MACROS {{{ */
 #ifndef PM_UPTODATE
 #define PM_UPTODATE     (1<<19) /* Parameter has up-to-date data (e.g. loaded from DB) */
 #endif
-
+/* }}} */
+/* DECLARATIONS {{{ */
 static Param createhash( char *name, int flags );
-static int append_tied_name( const char *name );
-static int remove_tied_name( const char *name );
-static char *unmetafy_zalloc(const char *to_copy, int *new_len);
-static void set_length(char *buf, int size);
+static int is_tied_cmd(char *pmname);
+static int is_tied(Param pm);
+
+static int no_database_action = 0;
+/* }}} */
 
 /*
  * Make sure we have all the bits I'm using for memory mapping, otherwise
@@ -52,7 +61,7 @@ static void set_length(char *buf, int size);
 
 #include <gdbm.h>
 
-static char *backtype = "db/gdbm";
+/* ARRAY: GSU {{{ */
 
 /*
  * Longer GSU structure, to carry GDBM_FILE of owning
@@ -72,25 +81,26 @@ static char *backtype = "db/gdbm";
  */
 
 struct gsu_scalar_ext {
-    struct gsu_scalar std;
-    GDBM_FILE dbf;
+    struct gsu_scalar std; /* Size of three pointers */
+    int type;
+    int use_cache;
+    int is_lazy;
     char *dbfile_path;
+    char *key;
+    size_t key_len;
+    char *password;
+    int fdesc;
+    GDBM_FILE dbf; /* a pointer */
 };
 
 /* Source structure - will be copied to allocated one,
  * with `dbf` filled. `dbf` allocation <-> gsu allocation. */
 static const struct gsu_scalar_ext gdbm_gsu_ext =
-{ { gdbmgetfn, gdbmsetfn, gdbmunsetfn }, 0, 0 };
+    { { gdbmgetfn, gdbmsetfn, gdbmunsetfn }, 0, 0 };
 
 /**/
 static const struct gsu_hash gdbm_hash_gsu =
-{ hashgetfn, gdbmhashsetfn, gdbmhashunsetfn };
-
-static struct builtin bintab[] = {
-    BUILTIN("ztie", 0, bin_ztie, 1, -1, 0, "d:f:r", NULL),
-    BUILTIN("zuntie", 0, bin_zuntie, 1, -1, 0, "u", NULL),
-    BUILTIN("zgdbmpath", 0, bin_zgdbmpath, 1, -1, 0, "", NULL),
-};
+    { hashgetfn, gdbmhashsetfn, gdbmhashunsetfn };
 
 #define ROARRPARAMDEF(name, var) \
     { name, PM_ARRAY | PM_READONLY, (void *) var, NULL,  NULL, NULL, NULL }
@@ -98,157 +108,242 @@ static struct builtin bintab[] = {
 /* Holds names of all tied parameters */
 char **zgdbm_tied;
 
-static struct paramdef patab[] = {
-    ROARRPARAMDEF( "zgdbm_tied", &zgdbm_tied ),
-};
+static struct paramdef patab[] =
+    { ROARRPARAMDEF( "zgdbm_tied", &zgdbm_tied ), };
+/* }}} */
+
+/* FUNCTION: gdbm_main_entry {{{ */
+static int
+gdbm_main_entry(VA_ALIST1(int cmd))
+    VA_DCL
+{
+    char *address = NULL, *pass = NULL, *pfile = NULL, *pmname = NULL, *key = NULL, *lazy = NULL;
+    int rdonly = 0, zcache = 0, pprompt = 0, rountie = 0;
+    char *argv[2];
+
+    va_list ap;
+    VA_DEF_ARG(int cmd);
+
+    VA_START(ap, cmd);
+    VA_GET_ARG(ap, cmd, int);
+
+    switch (cmd) {
+    case DB_TIE:
+        /* Order is:
+         * -a/f address, char *
+         * -r read-only, int
+         * -z zero-cache, int
+         * -p password, char *
+         * -P file with password, char *
+         * -l prompt for password, int
+         * -L lazy binding type, char *
+         * parameter name, char *
+         */
+        address = va_arg(ap, char *);
+        rdonly = va_arg(ap, int);
+        zcache = va_arg(ap, int);
+        pass = va_arg(ap, char *);
+        pfile = va_arg(ap, char *);
+        pprompt = va_arg(ap, int);
+        pmname = va_arg(ap, char *);
+        lazy = va_arg(ap, char *);
+        return zgtie_cmd(address, rdonly, zcache, pass, pfile, pprompt, pmname, lazy);
+
+    case DB_UNTIE:
+        /* Order is:
+         * -u untie read only parameter, int
+         * parameter name, char *
+         */
+        rountie = va_arg(ap, int);
+        pmname = va_arg(ap, char *);
+        argv[0] = pmname;
+        argv[1] = NULL;
+        return zguntie_cmd(rountie, argv);
+
+    case DB_IS_TIED:
+        /* Order is:
+         * parameter name, char *
+         */
+        pmname = va_arg(ap, char *);
+        return is_tied_cmd(pmname);
+
+    case DB_GET_ADDRESS:
+        /* Order is:
+         * Parameter name, char *
+         */
+        pmname = va_arg(ap, char*);
+        return gdbmpath_cmd(pmname);
+
+    case DB_CLEAR_CACHE:
+        /* Order is:
+         * Parameter name, char *
+         * key name, char *
+         */
+        pmname = va_arg(ap, char*);
+        key = va_arg(ap, char*);
+        return gdbmclear_cmd(pmname, key);
+
+    default:
+#ifdef DEBUG
+        dputs("Bad command %d in redis_main_entry", cmd);
+#endif
+        break;
+    }
+    return 1;
+}
+/* }}} */
+/* FUNCTION: zgtie_cmd {{{ */
 
 /**/
 static int
-bin_ztie(char *nam, char **args, Options ops, UNUSED(int func))
+zgtie_cmd(char *address, int rdonly, int zcache, char *pass, char *pfile, int pprompt, char *pmname, char *lazy)
 {
-    char *resource_name, *pmname;
     GDBM_FILE dbf = NULL;
     int read_write = GDBM_SYNC, pmflags = PM_REMOVABLE;
     Param tied_param;
+    struct gsu_scalar_ext *dbf_carrier;
 
-    if(!OPT_ISSET(ops,'d')) {
-        zwarnnam(nam, "you must pass `-d %s'", backtype);
-	return 1;
-    }
-    if(!OPT_ISSET(ops,'f')) {
-        zwarnnam(nam, "you must pass `-f' with a filename", NULL);
-	return 1;
-    }
-    if (OPT_ISSET(ops,'r')) {
-	read_write |= GDBM_READER;
-	pmflags |= PM_READONLY;
-    } else {
-	read_write |= GDBM_WRCREAT;
+    if (!address) {
+        zwarn("you must pass `-f' or '-a' path to the database", NULL);
+        return 1;
     }
 
-    /* Here should be a lookup of the backend type against
-     * a registry, if generam DB mechanism is to be added */
-    if (strcmp(OPT_ARG(ops, 'd'), backtype) != 0) {
-        zwarnnam(nam, "unsupported backend type `%s'", OPT_ARG(ops, 'd'));
-	return 1;
+    if (!pmname) {
+        zwarn("you must pass non-option argument - the target parameter to create, see -h");
+        return 1;
     }
 
-    resource_name = OPT_ARG(ops, 'f');
-    pmname = *args;
+    if (rdonly) {
+        read_write |= GDBM_READER;
+        pmflags |= PM_READONLY;
+    } else {
+        read_write |= GDBM_WRCREAT;
+    }
 
     if ((tied_param = (Param)paramtab->getnode(paramtab, pmname)) &&
-	!(tied_param->node.flags & PM_UNSET)) {
-	/*
-	 * Unset any existing parameter.  Note there's no implicit
-	 * "local" here, but if the existing parameter is local
-	 * then new parameter will be also local without following
+        !(tied_param->node.flags & PM_UNSET)) {
+        /*
+         * Unset any existing parameter.  Note there's no implicit
+         * "local" here, but if the existing parameter is local
+         * then new parameter will be also local without following
          * unset.
-	 *
-	 * We need to do this before attempting to open the DB
-	 * in case this variable is already tied to a DB.
-	 *
-	 * This can fail if the variable is readonly or restricted.
-	 * We could call unsetparam() and check errflag instead
-	 * of the return status.
-	 */
-	if (unsetparam_pm(tied_param, 0, 1))
-	    return 1;
-    }
-
-    gdbm_errno=0;
-    dbf = gdbm_open(resource_name, 0, read_write, 0666, 0);
+         *
+         * We need to do this before attempting to open the DB
+         * in case this variable is already tied to a DB.
+         *
+         * This can fail if the variable is readonly or restricted.
+         * We could call unsetparam() and check errflag instead
+         * of the return status.
+         */
+        if (unsetparam_pm(tied_param, 0, 1))
+            return 1;
+    }
+
+    gdbm_errno = 0;
+    dbf = gdbm_open(address, 0, read_write, 0666, 0);
     if(dbf == NULL) {
-	zwarnnam(nam, "error opening database file %s (%s)", resource_name, gdbm_strerror(gdbm_errno));
-	return 1;
+        zwarn("error opening database file %s (%s)", address, gdbm_strerror(gdbm_errno));
+        return 1;
     }
 
     if (!(tied_param = createhash(pmname, pmflags))) {
-        zwarnnam(nam, "cannot create the requested parameter %s", pmname);
-	fdtable[gdbm_fdesc(dbf)] = FDT_UNUSED;
-	gdbm_close(dbf);
-	return 1;
+        zwarn("cannot create the requested parameter %s", pmname);
+        gdbm_close(dbf);
+        return 1;
     }
 
-    addmodulefd(gdbm_fdesc(dbf), FDT_MODULE);
-    append_tied_name(pmname);
-
     tied_param->gsu.h = &gdbm_hash_gsu;
 
-    /* Allocate parameter sub-gsu, fill dbf field. 
+    /* Allocate parameter sub-gsu, fill dbf field.
      * dbf allocation is 1 to 1 accompanied by
      * gsu_scalar_ext allocation. */
 
-    struct gsu_scalar_ext *dbf_carrier = (struct gsu_scalar_ext *) zalloc(sizeof(struct gsu_scalar_ext));
+    dbf_carrier = (struct gsu_scalar_ext *) zshcalloc(sizeof(struct gsu_scalar_ext));
     dbf_carrier->std = gdbm_gsu_ext.std;
+    dbf_carrier->type = DB_KEY_TYPE_NO_KEY;
+    dbf_carrier->fdesc = gdbm_fdesc(dbf);
     dbf_carrier->dbf = dbf;
+    dbf_carrier->use_cache = 1;
+    if (zcache) {
+        dbf_carrier->use_cache = 0;
+    }
+    if (lazy) {
+        dbf_carrier->is_lazy = 1;
+    }
     tied_param->u.hash->tmpdata = (void *)dbf_carrier;
 
     /* Fill also file path field */
-    if (*resource_name != '/') {
+    if (*address != '/') {
         /* Code copied from check_autoload() */
-        resource_name = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", resource_name);
-        resource_name = xsymlink(resource_name, 1);
+        address = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", address);
+        address = xsymlink(address, 1);
     }
-    dbf_carrier->dbfile_path = ztrdup(resource_name);
+    dbf_carrier->dbfile_path = ztrdup(address);
+
+    addmodulefd(dbf_carrier->fdesc, FDT_INTERNAL);
+    zsh_db_arr_append(&zgdbm_tied, pmname);
+
     return 0;
 }
+/* }}} */
+/* FUNCTION: zguntie_cmd {{{ */
 
 /**/
 static int
-bin_zuntie(char *nam, char **args, Options ops, UNUSED(int func))
+zguntie_cmd(int rountie, char **args)
 {
     Param pm;
     char *pmname;
     int ret = 0;
 
     for (pmname = *args; *args++; pmname = *args) {
-	pm = (Param) paramtab->getnode(paramtab, pmname);
-	if(!pm) {
-	    zwarnnam(nam, "cannot untie %s", pmname);
-	    ret = 1;
-	    continue;
-	}
-	if (pm->gsu.h != &gdbm_hash_gsu) {
-	    zwarnnam(nam, "not a tied gdbm hash: %s", pmname);
-	    ret = 1;
-	    continue;
-	}
-
-	queue_signals();
-	if (OPT_ISSET(ops,'u'))
-	    gdbmuntie(pm);	/* clear read-only-ness */
-	if (unsetparam_pm(pm, 0, 1)) {
-	    /* assume already reported */
-	    ret = 1;
-	}
-	unqueue_signals();
+        pm = (Param) paramtab->getnode(paramtab, pmname);
+        if(!pm) {
+            zwarn("cannot untie %s", pmname);
+            ret = 1;
+            continue;
+        }
+        if (pm->gsu.h != &gdbm_hash_gsu) {
+            zwarn("not a tied gdbm hash: %s", pmname);
+            ret = 1;
+            continue;
+        }
+
+        queue_signals();
+        if (rountie) {
+            pm->node.flags &= ~PM_READONLY;
+        }
+        if (unsetparam_pm(pm, 0, 1)) {
+            /* assume already reported */
+            ret = 1;
+        }
+        unqueue_signals();
     }
 
     return ret;
 }
+/* }}} */
+/* FUNCTION: gdbmpath_cmd{{{ */
 
 /**/
 static int
-bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
+gdbmpath_cmd(char *pmname)
 {
     Param pm;
-    char *pmname;
-
-    pmname = *args;
 
     if (!pmname) {
-        zwarnnam(nam, "parameter name (whose path is to be written to $REPLY) is required");
+        zwarn("parameter name (whose path is to be written to $REPLY) not given");
         return 1;
     }
 
     pm = (Param) paramtab->getnode(paramtab, pmname);
     if(!pm) {
-        zwarnnam(nam, "no such parameter: %s", pmname);
+        zwarn("no such parameter: %s", pmname);
         return 1;
     }
 
     if (pm->gsu.h != &gdbm_hash_gsu) {
-        zwarnnam(nam, "not a tied gdbm parameter: %s", pmname);
+        zwarn("not a tied gdbm parameter: %s", pmname);
         return 1;
     }
 
@@ -261,6 +356,49 @@ bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
 
     return 0;
 }
+/* }}} */
+/* FUNCTION: gdbmclear_cmd {{{ */
+
+/**/
+static int
+gdbmclear_cmd(char *pmname, char *key)
+{
+    Param pm, val_pm;
+    HashTable ht;
+    HashNode hn;
+
+    if (!pmname) {
+        zwarn("parameter name (whose read-cache is to be cleared) is required");
+        return 1;
+    }
+    if (!key) {
+        zwarn("hash-key (whose read-cache is to be cleared) is required");
+        return 1;
+    }
+
+    pm = (Param) paramtab->getnode(paramtab, pmname);
+    if(!pm) {
+        zwarnnam("no such parameter: %s", pmname);
+        return 1;
+    }
+
+    if (pm->gsu.h != &gdbm_hash_gsu) {
+        zwarnnam("not a tied gdbm parameter: %s", pmname);
+        return 1;
+    }
+
+    ht = pm->u.hash;
+    hn = gethashnode2( ht, key );
+    val_pm = (Param) hn;
+    if (val_pm) {
+        val_pm->node.flags &= ~(PM_UPTODATE);
+    }
+
+    return 0;
+}
+/* }}} */
+
+/* FUNCTION: gdbmgetfn {{{ */
 
 /*
  * The param is actual param in hash – always, because
@@ -269,9 +407,7 @@ bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
  * wasn't yet queried.
  *
  * It will be left in this state if database doesn't
- * contain such key. That might be a drawback, maybe
- * setting to empty value has sense, as no other writer
- * can exist. This would remove subtle hcalloc(1) leak.
+ * contain such key.
  */
 
 /**/
@@ -279,7 +415,8 @@ static char *
 gdbmgetfn(Param pm)
 {
     datum key, content;
-    int ret;
+    int ret, umlen;
+    char *umkey;
     GDBM_FILE dbf;
 
     /* Key already retrieved? There is no sense of asking the
@@ -291,14 +428,14 @@ gdbmgetfn(Param pm)
      * - if we are writers, we for sure have newest copy of data
      * - if we are readers, we for sure have newest copy of data
      */
-    if ( pm->node.flags & PM_UPTODATE ) {
-        return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+    if ((pm->node.flags & PM_UPTODATE) && ((struct gsu_scalar_ext *)pm->gsu.s)->use_cache) {
+        return pm->u.str ? pm->u.str : "";
     }
 
     /* Unmetafy key. GDBM fits nice into this
      * process, as it uses length of data */
-    int umlen = 0;
-    char *umkey = unmetafy_zalloc(pm->node.nam,&umlen);
+    umlen = 0;
+    umkey = zsh_db_unmetafy_zalloc(pm->node.nam,&umlen);
 
     key.dptr = umkey;
     key.dsize = umlen;
@@ -319,9 +456,11 @@ gdbmgetfn(Param pm)
         /* Metafy returned data. All fits - metafy
          * can obtain data length to avoid using \0 */
         pm->u.str = metafy(content.dptr, content.dsize, META_DUP);
+        /* gdbm allocates with malloc */
+        free(content.dptr);
 
         /* Free key, restoring its original length */
-        set_length(umkey, umlen);
+        zsh_db_set_length(umkey, umlen);
         zsfree(umkey);
 
         /* Can return pointer, correctly saved inside hash */
@@ -329,12 +468,13 @@ gdbmgetfn(Param pm)
     }
 
     /* Free key, restoring its original length */
-    set_length(umkey, umlen);
+    zsh_db_set_length(umkey, umlen);
     zsfree(umkey);
 
-    /* Can this be "" ? */
-    return (char *) hcalloc(1);
+    return "";
 }
+/* }}} */
+/* FUNCTION: gdbmsetfn {{{ */
 
 /**/
 static void
@@ -361,16 +501,16 @@ gdbmsetfn(Param pm, char *val)
 
     /* Database */
     dbf = ((struct gsu_scalar_ext *)pm->gsu.s)->dbf;
-    if (dbf) {
+    if (dbf && no_database_action == 0) {
         int umlen = 0;
-        char *umkey = unmetafy_zalloc(pm->node.nam,&umlen);
+        char *umkey = zsh_db_unmetafy_zalloc(pm->node.nam,&umlen);
 
         key.dptr = umkey;
         key.dsize = umlen;
 
         if (val) {
             /* Unmetafy with exact zalloc size */
-            char *umval = unmetafy_zalloc(val,&umlen);
+            char *umval = zsh_db_unmetafy_zalloc(val,&umlen);
 
             /* Store */
             content.dptr = umval;
@@ -378,18 +518,19 @@ gdbmsetfn(Param pm, char *val)
             (void)gdbm_store(dbf, key, content, GDBM_REPLACE);
 
             /* Free */
-            set_length(umval, umlen);
+            zsh_db_set_length(umval, umlen);
             zsfree(umval);
         } else {
             (void)gdbm_delete(dbf, key);
         }
 
         /* Free key */
-        set_length(umkey, key.dsize);
+        zsh_db_set_length(umkey, key.dsize);
         zsfree(umkey);
     }
 }
-
+/* }}} */
+/* FUNCTION: gdbmunsetfn {{{ */
 /**/
 static void
 gdbmunsetfn(Param pm, UNUSED(int um))
@@ -397,6 +538,9 @@ gdbmunsetfn(Param pm, UNUSED(int um))
     /* Set with NULL */
     gdbmsetfn(pm, NULL);
 }
+/* }}} */
+
+/* FUNCTION: getgdbmnode {{{ */
 
 /**/
 static HashNode
@@ -427,17 +571,19 @@ getgdbmnode(HashTable ht, const char *name)
         val_pm = (Param) zshcalloc( sizeof (*val_pm) );
         val_pm->node.flags = PM_SCALAR | PM_HASHELEM; /* no PM_UPTODATE */
         val_pm->gsu.s = (GsuScalar) ht->tmpdata;
-        ht->addnode( ht, ztrdup( name ), val_pm ); // sets pm->node.nam
+        ht->addnode( ht, ztrdup( name ), val_pm ); /* sets pm->node.nam */
     }
 
     return (HashNode) val_pm;
 }
+/* }}} */
+/* FUNCTION: scangdbmkeys {{{ */
 
 /**/
 static void
 scangdbmkeys(HashTable ht, ScanFunc func, int flags)
 {
-    datum key;
+    datum key, prev_key;
     GDBM_FILE dbf = ((struct gsu_scalar_ext *)ht->tmpdata)->dbf;
 
     /* Iterate keys adding them to hash, so
@@ -452,14 +598,19 @@ scangdbmkeys(HashTable ht, ScanFunc func, int flags)
         HashNode hn = getgdbmnode(ht, zkey);
         zsfree( zkey );
 
-	func(hn, flags);
+        func(hn, flags);
 
         /* Iterate - no problem as interfacing Param
          * will do at most only fetches, not stores */
+        prev_key = key;
         key = gdbm_nextkey(dbf, key);
+        free(prev_key.dptr);
     }
 
 }
+/* }}} */
+
+/* FUNCTION: gdbmhashsetfn {{{ */
 
 /*
  * Replace database with new hash
@@ -475,83 +626,97 @@ gdbmhashsetfn(Param pm, HashTable ht)
     datum key, content;
 
     if (!pm->u.hash || pm->u.hash == ht)
-	return;
+        return;
 
     if (!(dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf))
-	return;
+        return;
 
     key = gdbm_firstkey(dbf);
     while (key.dptr) {
-	queue_signals();
-	(void)gdbm_delete(dbf, key);
-	free(key.dptr);
-	unqueue_signals();
-	key = gdbm_firstkey(dbf);
+        queue_signals();
+        (void)gdbm_delete(dbf, key);
+        free(key.dptr);
+        unqueue_signals();
+        key = gdbm_firstkey(dbf);
     }
 
-    /* just deleted everything, clean up */
-    (void)gdbm_reorganize(dbf);
+    /* Just deleted everything, clean up if no new data.
+     * User can also reorganize via gdbmtool. */
+    if (!ht || ht->hsize == 0) {
+        (void)gdbm_reorganize(dbf);
+    }
+
+    no_database_action = 1;
+    emptyhashtable(pm->u.hash);
+    no_database_action = 0;
 
     if (!ht)
-	return;
+        return;
 
-     /* Put new strings into database, waiting
-      * for their interfacing-Params to be created */
+    /* Put new strings into database, waiting
+     * for their interfacing-Params to be created */
 
-    for (i = 0; i < ht->hsize; i++)
-	for (hn = ht->nodes[i]; hn; hn = hn->next) {
-	    struct value v;
+    for (i = 0; i < ht->hsize; i++) {
+        for (hn = ht->nodes[i]; hn; hn = hn->next) {
+            struct value v;
+            int umlen = 0;
+            char *umkey, *umval;
 
-	    v.isarr = v.flags = v.start = 0;
-	    v.end = -1;
-	    v.arr = NULL;
-	    v.pm = (Param) hn;
+            v.isarr = v.flags = v.start = 0;
+            v.end = -1;
+            v.arr = NULL;
+            v.pm = (Param) hn;
 
             /* Unmetafy key */
-            int umlen = 0;
-            char *umkey = unmetafy_zalloc(v.pm->node.nam,&umlen);
+            umkey = zsh_db_unmetafy_zalloc(v.pm->node.nam,&umlen);
 
-	    key.dptr = umkey;
-	    key.dsize = umlen;
+            key.dptr = umkey;
+            key.dsize = umlen;
 
-	    queue_signals();
+            queue_signals();
 
             /* Unmetafy */
-            char *umval = unmetafy_zalloc(getstrvalue(&v),&umlen);
+            umval = zsh_db_unmetafy_zalloc(getstrvalue(&v),&umlen);
 
             /* Store */
-	    content.dptr = umval;
-	    content.dsize = umlen;
-	    (void)gdbm_store(dbf, key, content, GDBM_REPLACE);	
+            content.dptr = umval;
+            content.dsize = umlen;
+            (void)gdbm_store(dbf, key, content, GDBM_REPLACE);
 
-            /* Free - unmetafy_zalloc allocates exact required
+            /* Free - zsh_db_unmetafy_zalloc allocates exact required
              * space, however unmetafied string can have zeros
              * in content, so we must first fill with non-0 bytes */
-            set_length(umval, content.dsize);
+            zsh_db_set_length(umval, content.dsize);
             zsfree(umval);
-            set_length(umkey, key.dsize);
+            zsh_db_set_length(umkey, key.dsize);
             zsfree(umkey);
 
-	    unqueue_signals();
-	}
+            unqueue_signals();
+        }
+    }
+    /* We reuse our hash, the input is to be deleted */
+    deleteparamtable(ht);
 }
+/* }}} */
+/* FUNCTION: gdbmuntie {{{*/
 
 /**/
 static void
 gdbmuntie(Param pm)
 {
-    GDBM_FILE dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf;
+    struct gsu_scalar_ext *gsu_ext = (struct gsu_scalar_ext *)pm->u.hash->tmpdata;
+    GDBM_FILE dbf = gsu_ext->dbf;
     HashTable ht = pm->u.hash;
 
     if (dbf) { /* paranoia */
-	fdtable[gdbm_fdesc(dbf)] = FDT_UNUSED;
+        fdtable[gsu_ext->fdesc] = FDT_UNUSED;
         gdbm_close(dbf);
 
         /* Let hash fields know there's no backend */
         ((struct gsu_scalar_ext *)ht->tmpdata)->dbf = NULL;
 
         /* Remove from list of tied parameters */
-        remove_tied_name(pm->node.nam);
+        zsh_db_filter_arr(&zgdbm_tied, pm->node.nam);
     }
 
     /* for completeness ... createspecialhash() should have an inverse */
@@ -561,19 +726,22 @@ gdbmuntie(Param pm)
     pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
     pm->gsu.h = &stdhash_gsu;
 }
-
+/* }}} */
+/* FUNCTION: gdbmhashunsetfn {{{ */
 /**/
 static void
 gdbmhashunsetfn(Param pm, UNUSED(int exp))
 {
+    struct gsu_scalar_ext * gsu_ext;
+
     gdbmuntie(pm);
 
     /* Remember custom GSU structure assigned to
      * u.hash->tmpdata before hash gets deleted */
-    struct gsu_scalar_ext * gsu_ext = pm->u.hash->tmpdata;
+    gsu_ext = pm->u.hash->tmpdata;
 
-    /* Uses normal unsetter. Will delete all owned
-     * parameters and also hashtable. */
+    /* Uses normal unsetter (because gdbmuntie is called above).
+     * Will delete all owned field-parameters and also hashtable. */
     pm->gsu.h->setfn(pm, NULL);
 
     /* Don't need custom GSU structure with its
@@ -583,14 +751,20 @@ gdbmhashunsetfn(Param pm, UNUSED(int exp))
 
     pm->node.flags |= PM_UNSET;
 }
+/* }}} */
 
-static struct features module_features = {
-    bintab, sizeof(bintab)/sizeof(*bintab),
+/* ARRAY: module_features {{{ */
+static struct features module_features =
+{
+    NULL, 0,
     NULL, 0,
     NULL, 0,
     patab, sizeof(patab)/sizeof(*patab),
     0
 };
+/* }}} */
+
+/* FUNCTION: setup_ {{{ */
 
 /**/
 int
@@ -598,7 +772,8 @@ setup_(UNUSED(Module m))
 {
     return 0;
 }
-
+/* }}} */
+/* FUNCTION: features_ {{{ */
 /**/
 int
 features_(Module m, char ***features)
@@ -606,6 +781,8 @@ features_(Module m, char ***features)
     *features = featuresarray(m, &module_features);
     return 0;
 }
+/* }}} */
+/* FUNCTION: enables_ {{{ */
 
 /**/
 int
@@ -613,34 +790,45 @@ enables_(Module m, int **enables)
 {
     return handlefeatures(m, &module_features, enables);
 }
+/* }}} */
+/* FUNCTION: boot_ {{{ */
 
 /**/
 int
 boot_(UNUSED(Module m))
 {
     zgdbm_tied = zshcalloc((1) * sizeof(char *));
+    zsh_db_register_backend("db/gdbm", gdbm_main_entry);
     return 0;
 }
+/* }}} */
+/* FUNCTION: cleanup_ {{{ */
 
 /**/
 int
 cleanup_(Module m)
 {
+    zsh_db_unregister_backend("db/gdbm");
+
     /* This frees `zgdbm_tied` */
     return setfeatureenables(m, &module_features, NULL);
 }
-
+/* }}} */
+/* FUNCTION: finish_ {{{ */
 /**/
 int
 finish_(UNUSED(Module m))
 {
     return 0;
 }
+/* }}} */
 
 /*********************
  * Utility functions *
  *********************/
 
+/* FUNCTION: createhash {{{ */
+
 static Param createhash( char *name, int flags ) {
     Param pm;
     HashTable ht;
@@ -651,137 +839,51 @@ static Param createhash( char *name, int flags ) {
     }
 
     if (pm->old)
-	pm->level = locallevel;
+        pm->level = locallevel;
 
     /* This creates standard hash. */
-    ht = pm->u.hash = newparamtable(32, name);
+    ht = pm->u.hash = newparamtable(17, name);
     if (!pm->u.hash) {
         paramtab->removenode(paramtab, name);
         paramtab->freenode(&pm->node);
-        zwarnnam(name, "Out of memory when allocating hash");
+        zwarnnam(name, "out of memory when allocating hash");
+        return NULL;
     }
 
+    /* Does free Param (unsetfn is called) */
+    ht->freenode = zsh_db_freeparamnode;
+
     /* These provide special features */
     ht->getnode = ht->getnode2 = getgdbmnode;
     ht->scantab = scangdbmkeys;
 
     return pm;
 }
-
-/*
- * Adds parameter name to `zgdbm_tied`
- */
-
-static int append_tied_name( const char *name ) {
-    int old_len = arrlen(zgdbm_tied);
-    char **new_zgdbm_tied = zshcalloc( (old_len+2) * sizeof(char *));
-
-    /* Copy */
-    char **p = zgdbm_tied;
-    char **dst = new_zgdbm_tied;
-    while (*p) {
-        *dst++ = *p++;
+/* }}} */
+/* FUNCTION: is_tied_cmd {{{ */
+static int
+is_tied_cmd(char *pmname)
+{
+    Param pm = (Param) paramtab->getnode(paramtab, pmname);
+    if(!pm) {
+        return 1; /* false */
     }
 
-    /* Append new one */
-    *dst = ztrdup(name);
-
-    /* Substitute, free old one */
-    zfree(zgdbm_tied, sizeof(char *) * (old_len + 1));
-    zgdbm_tied = new_zgdbm_tied;
-
-    return 0;
+    return 1 - is_tied(pm); /* negation for shell-code */
 }
+/* }}} */
+/* FUNCTION: is_tied {{{ */
 
-/*
- * Removes parameter name from `zgdbm_tied`
- */
-
-static int remove_tied_name( const char *name ) {
-    int old_len = arrlen(zgdbm_tied);
-
-    /* Two stage, to always have arrlen() == zfree-size - 1.
-     * Could do allocation and revert when `not found`, but
-     * what would be better about that. */
-
-    /* Find one to remove */
-    char **p = zgdbm_tied;
-    while (*p) {
-        if (0==strcmp(name,*p)) {
-            break;
-        }
-        p++;
-    }
-
-    /* Copy x+1 to x */
-    while (*p) {
-        *p=*(p+1);
-        p++;
-    }
-
-    /* Second stage. Size changed? Only old_size-1
-     * change is possible, but.. paranoia way */
-    int new_len = arrlen(zgdbm_tied);
-    if (new_len != old_len) {
-        char **new_zgdbm_tied = zshcalloc((new_len+1) * sizeof(char *));
-
-        /* Copy */
-        p = zgdbm_tied;
-        char **dst = new_zgdbm_tied;
-        while (*p) {
-            *dst++ = *p++;
-        }
-        *dst = NULL;
-
-        /* Substitute, free old one */
-        zfree(zgdbm_tied, sizeof(char *) * (old_len + 1));
-        zgdbm_tied = new_zgdbm_tied;
+static int
+is_tied(Param pm)
+{
+    if (pm->gsu.h == &gdbm_hash_gsu ) {
+        return 1;
     }
 
     return 0;
 }
-
-/*
- * Unmetafy that:
- * - duplicates bufer to work on it,
- * - does zalloc of exact size for the new string,
- * - restores work buffer to original content, to restore strlen
- *
- * No zsfree()-confusing string will be produced.
- */
-static char *unmetafy_zalloc(const char *to_copy, int *new_len) {
-    char *work, *to_return;
-    int my_new_len = 0;
-
-    work = ztrdup(to_copy);
-    work = unmetafy(work,&my_new_len);
-
-    if (new_len)
-        *new_len = my_new_len;
-
-    /* This string can be correctly zsfree()-d */
-    to_return = (char *) zalloc((my_new_len+1)*sizeof(char));
-    memcpy(to_return, work, sizeof(char)*my_new_len); // memcpy handles $'\0'
-    to_return[my_new_len]='\0';
-
-    /* Restore original strlen and correctly free */
-    strcpy(work, to_copy);
-    zsfree(work);
-
-    return to_return;
-}
-
-/*
- * For zsh-allocator, rest of Zsh seems to use
- * free() instead of zsfree(), and such length
- * restoration causes slowdown, but all is this
- * way strict - correct */
-static void set_length(char *buf, int size) {
-    buf[size]='\0';
-    while ( -- size >= 0 ) {
-        buf[size]=' ';
-    }
-}
+/* }}} */
 
 #else
 # error no gdbm
diff --git a/Src/Modules/db_gdbm.mdd b/Src/Modules/db_gdbm.mdd
index 210c221..4667222 100644
--- a/Src/Modules/db_gdbm.mdd
+++ b/Src/Modules/db_gdbm.mdd
@@ -7,6 +7,8 @@ fi
 '
 load=no
 
-autofeatures="b:ztie b:zuntie b:zgdbmpath p:zgdbm_tied"
+autofeatures="p:zgdbm_tied"
+
+moddeps="zsh/db"
 
 objects="db_gdbm.o"
diff --git a/Test/V11db_gdbm.ztst b/Test/V11db_gdbm.ztst
index 6d74cef..499108e 100644
--- a/Test/V11db_gdbm.ztst
+++ b/Test/V11db_gdbm.ztst
@@ -4,16 +4,21 @@
 
 %prep
 
- modname="zsh/db/gdbm"
+ module_path=( `pwd`/Modules )
+ modname1="zsh/db"
+ modname2="zsh/db/gdbm"
  dbfile=db.gdbm
- if ! zmodload $modname 2>/dev/null; then
-   ZTST_unimplemented="can't load $modname module for testing"
+ if ! zmodload $modname1 2>/dev/null; then
+   ZTST_unimplemented="can't load $modname1 module for testing"
+ fi
+ if ! zmodload $modname2 2>/dev/null; then
+   ZTST_unimplemented="can't load $modname2 module for testing"
  fi
  rm -f db.gdbm
 
 %test
 
- (zmodload -u $modname && zmodload $modname)
+ (zmodload -u $modname2 && zmodload $modname2)
 0:unload and reload the module without crashing
 
  ztie -d db/gdbm -f $dbfile dbase
@@ -304,23 +309,40 @@
 >漢字
 
  ztie -d db/gdbm -f $dbfile dbase
- zgdbmpath dbase
+ ztaddress dbase
  [[ $REPLY = */Test/db.gdbm ]] && echo correct
  zuntie dbase
  ztie -r -d db/gdbm -f $dbfile dbase
- zgdbmpath dbase
+ ztaddress dbase
  [[ $REPLY = */Test/db.gdbm ]] && echo correct
  zuntie -u dbase
-0:zgdbmpath builtin
+0:ztaddress builtin
 >correct
 >correct
 
  ztie -d db/gdbm -f $dbfile dbase
+ dbase[testkey]=value1
  fun() { while read line; do echo $line; done }
- eval "dbase[testkey]=value1" | fun
+ eval "dbase[testkey]=value2" | fun
+ echo $dbase[testkey]
+ ztclear dbase testkey
  echo $dbase[testkey]
 0:Test store in forked Zsh
 >value1
+>value2
+
+ ztie -d db/gdbm -f $dbfile -z dbase
+ dbase[testkey]=value1
+ fun() { while read line; do echo $line; done }
+ eval "dbase[testkey]=value2" | fun
+ echo $dbase[testkey]
+0:Test store in forked Zsh, with -z zero-cache option
+>value2
+
+ print -rl -- "${(okv@)zdb_backends}"
+0:Test zdb_backends
+>db/gdbm
+>loaded
 
 %clean
 
