From: Sebastian Gniazdowski <psprint@zdharma.org>
To: zsh-workers@zsh.org
Subject: [PATCH] 2 modules, zsh/db, zsh/gdbm, bug-fixes
Date: Thu, 22 Jun 2017 11:04:48 +0200 [thread overview]
Message-ID: <etPan.594b8830.59cfcf4a.63ea@zdharma.org> (raw)
[-- Attachment #1: Type: text/plain, Size: 1148 bytes --]
Hello
1. New module `zsh/db` that provides good architecture, via `*_main_entry` functions in client modules (zsh/db/gdbm in this patch).
2. Also, `zdb_backends` hash holding e.g. "db/gdbm" -> "loaded".
These are according to Bart opinion. Amount of builtins is normal, small (ztie, zuntie, etc). Also, zsh/db behaves as a library of common functions.
When developing zredis, with intensive Valgrind use, I've found and fixed following bugs in zsh/gdbm:
- no deletehashtable() in custom hash-set-fn,
- no emptyhashtable() in custom hash-set-fn – replacing hash with tied_param=( k v ) caused addition of k -> v, and left untouched previous Param content,
- call to *untie function to remove PM_READONLY flag caused memory leak, the flag is now removed directly, if user gave -u option to untie,
- data returned by libgdbm should be free()-d (e.g. key returned by gdbm_nextkey),
- freeparamnode() had if(delunset) condition, this caused memory leak – replaced with myfreeparamnode().
Tested on: OS X, Freebsd, Gentoo, Ubuntu.
Next step would be zredis.
--
Sebastian Gniazdowski
psprint /at/ zdharma.org
[-- Attachment #2: db_gdbm_2_modules.diff --]
[-- Type: application/octet-stream, Size: 51824 bytes --]
diff --git a/Src/Modules/db.c b/Src/Modules/db.c
new file mode 100644
index 0000000..1af0fc2
--- /dev/null
+++ b/Src/Modules/db.c
@@ -0,0 +1,799 @@
+/* -*- 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.
+ */
+
+#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");
+ 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("zsh/db: Parameter zdb_backends is defined by user, will not update it");
+ return;
+ }
+
+ HashTable ht = pm->u.hash;
+ HashNode hn = gethashnode2(ht, id);
+ Param 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;
+
+ /* 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;
+ }
+
+ /* Prepare arguments for backend */
+
+ char *address = NULL, *pass = NULL, *pfile = NULL, *lazy = NULL;
+ int rdonly = 0, zcache = 0, pprompt = 0;
+
+ /* 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;
+
+ 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;
+ }
+
+ int rountie = 0;
+
+ 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;
+}
+/* }}} */
+
+/* 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);
+}
+/* }}} */
+
+/*************** 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: 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));
+}
+/* }}} */
+/* 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);
+
+ /* 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 */
+ int new_len = arrlen(*arr);
+ if (new_len != old_len) {
+ char **new_arr = (char **) zshcalloc((new_len+1) * sizeof(char *));
+
+ /* Copy */
+ p = *arr;
+ char **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;
+}
+/* }}} */
+
+/***************** 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);
+}
+/* }}} */
+
diff --git a/Src/Modules/db.h b/Src/Modules/db.h
new file mode 100644
index 0000000..eb1183d
--- /dev/null
+++ b/Src/Modules/db.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+/* 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..5949e80 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
*
@@ -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,241 @@ 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;
+
+ 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 *);
+ char *argv[2];
+ 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;
- 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;
+ *
+ * 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);
+ 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));
+ 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 +355,46 @@ bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
return 0;
}
+/* }}} */
+/* FUNCTION: bin_zgdbmclear {{{ */
+
+/**/
+static int
+gdbmclear_cmd(char *pmname, char *key)
+{
+ Param pm;
+
+ 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;
+ }
+
+ HashTable ht = pm->u.hash;
+ HashNode hn = gethashnode2( ht, key );
+ Param 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 +403,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.
*/
/**/
@@ -291,14 +423,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,¨en);
+ char *umkey = zsh_db_unmetafy_zalloc(pm->node.nam,¨en);
key.dptr = umkey;
key.dsize = umlen;
@@ -319,9 +451,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 +463,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 +496,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,¨en);
+ char *umkey = zsh_db_unmetafy_zalloc(pm->node.nam,¨en);
key.dptr = umkey;
key.dsize = umlen;
if (val) {
/* Unmetafy with exact zalloc size */
- char *umval = unmetafy_zalloc(val,¨en);
+ char *umval = zsh_db_unmetafy_zalloc(val,¨en);
/* Store */
content.dptr = umval;
@@ -378,18 +513,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 +533,8 @@ gdbmunsetfn(Param pm, UNUSED(int um))
/* Set with NULL */
gdbmsetfn(pm, NULL);
}
+/* }}} */
+/* FUNCTION: getgdbmnode {{{ */
/**/
static HashNode
@@ -432,12 +570,14 @@ getgdbmnode(HashTable ht, const char *name)
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 +592,18 @@ 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 +619,99 @@ 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.
+ * Turned off waiting for gdbm to fix NFS problem.
+ * User can reorganize via gdbmtool. */
+#if 0
+ if (GDBM_VERSION_MAJOR > 1 || (GDBM_VERSION_MAJOR == 1 && GDBM_VERSION_MINOR > 13) ) {
+ (void)gdbm_reorganize(dbf);
}
+#endif
- /* just deleted everything, clean up */
- (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;
- 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,¨en);
+ char *umkey = zsh_db_unmetafy_zalloc(v.pm->node.nam,¨en);
- key.dptr = umkey;
- key.dsize = umlen;
+ key.dptr = umkey;
+ key.dsize = umlen;
- queue_signals();
+ queue_signals();
/* Unmetafy */
- char *umval = unmetafy_zalloc(getstrvalue(&v),¨en);
+ char *umval = zsh_db_unmetafy_zalloc(getstrvalue(&v),¨en);
/* 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,7 +721,8 @@ 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))
@@ -572,8 +733,8 @@ gdbmhashunsetfn(Param pm, UNUSED(int exp))
* u.hash->tmpdata before hash gets deleted */
struct gsu_scalar_ext * 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 +744,19 @@ 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 +764,8 @@ setup_(UNUSED(Module m))
{
return 0;
}
-
+/* }}} */
+/* FUNCTION: features_ {{{ */
/**/
int
features_(Module m, char ***features)
@@ -606,6 +773,8 @@ features_(Module m, char ***features)
*features = featuresarray(m, &module_features);
return 0;
}
+/* }}} */
+/* FUNCTION: enables_ {{{ */
/**/
int
@@ -613,34 +782,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 +831,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");
+ 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
next reply other threads:[~2017-06-22 9:16 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-06-22 9:04 Sebastian Gniazdowski [this message]
2017-06-22 9:05 ` Sebastian Gniazdowski
2017-06-23 9:29 ` Bart Schaefer
2017-06-23 8:36 ` Bart Schaefer
2017-06-23 21:30 ` Daniel Shahaf
2017-06-24 3:53 ` Bart Schaefer
2017-06-24 4:56 ` Sebastian Gniazdowski
2017-06-25 0:50 ` Daniel Shahaf
2017-06-26 2:40 ` Sebastian Gniazdowski
2017-06-24 17:36 ` Bart Schaefer
2017-06-25 8:06 ` Sebastian Gniazdowski
2017-06-25 11:22 ` Sebastian Gniazdowski
2017-06-25 22:52 ` Bart Schaefer
2017-06-27 5:46 ` Sebastian Gniazdowski
2017-06-27 8:39 ` Daniel Shahaf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=etPan.594b8830.59cfcf4a.63ea@zdharma.org \
--to=psprint@zdharma.org \
--cc=zsh-workers@zsh.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.vuxu.org/mirror/zsh/
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).