1 /*
2  * db_gdbm.c - bindings for gdbm
3  *
4  * This file is part of zsh, the Z shell.
5  *
6  * Copyright (c) 2008 Clint Adams
7  * All rights reserved.
8  *
9  * Modifications copyright (c) 2017 Sebastian Gniazdowski
10  * All rights reserved.
11  *
12  * Permission is hereby granted, without written agreement and without
13  * license or royalty fees, to use, copy, modify, and distribute this
14  * software and to distribute modified versions of this software for any
15  * purpose, provided that the above copyright notice and the following
16  * two paragraphs appear in all copies of this software.
17  *
18  * In no event shall Clint Adams or the Zsh Development
19  * Group be liable to any party for direct, indirect, special, incidental, or
20  * consequential damages arising out of the use of this software and its
21  * documentation, even if Peter Stephenson, Sven Wischnowsky and the Zsh
22  * Development Group have been advised of the possibility of such damage.
23  *
24  * Clint Adams and the Zsh Development Group
25  * specifically disclaim any warranties, including, but not limited to, the
26  * implied warranties of merchantability and fitness for a particular purpose.
27  * The software provided hereunder is on an "as is" basis, and Peter
28  * Stephenson, Sven Wischnowsky and the Zsh Development Group have no
29  * obligation to provide maintenance, support, updates, enhancements, or
30  * modifications.
31  *
32  */
33 
34 #include "db_gdbm.mdh"
35 #include "db_gdbm.pro"
36 
37 #ifndef PM_UPTODATE
38 #define PM_UPTODATE     (1<<19) /* Parameter has up-to-date data (e.g. loaded from DB) */
39 #endif
40 
41 static Param createhash( char *name, int flags );
42 static int append_tied_name( const char *name );
43 static int remove_tied_name( const char *name );
44 static char *unmetafy_zalloc(const char *to_copy, int *new_len);
45 static void myfreeparamnode(HashNode hn);
46 
47 static int no_database_action = 0;
48 
49 /*
50  * Make sure we have all the bits I'm using for memory mapping, otherwise
51  * I don't know what I'm doing.
52  */
53 #if defined(HAVE_GDBM_H) && defined(HAVE_GDBM_OPEN)
54 
55 #include <gdbm.h>
56 
57 static char *backtype = "db/gdbm";
58 
59 /*
60  * Longer GSU structure, to carry GDBM_FILE of owning
61  * database. Every parameter (hash value) receives GSU
62  * pointer and thus also receives GDBM_FILE - this way
63  * parameters can access proper database.
64  *
65  * Main HashTable parameter has the same instance of
66  * the custom GSU struct in u.hash->tmpdata field.
67  * When database is closed, `dbf` field is set to NULL
68  * and hash values know to not access database when
69  * being unset (total purge at zuntie).
70  *
71  * When database closing is ended, custom GSU struct
72  * is freed. Only new ztie creates new custom GSU
73  * struct instance.
74  */
75 
76 struct gsu_scalar_ext {
77     struct gsu_scalar std; /* Size of three pointers */
78     GDBM_FILE dbf;
79     char *dbfile_path;
80 };
81 
82 /* Source structure - will be copied to allocated one,
83  * with `dbf` filled. `dbf` allocation <-> gsu allocation. */
84 static const struct gsu_scalar_ext gdbm_gsu_ext =
85 { { gdbmgetfn, gdbmsetfn, gdbmunsetfn }, 0, 0 };
86 
87 /**/
88 static const struct gsu_hash gdbm_hash_gsu =
89 { hashgetfn, gdbmhashsetfn, gdbmhashunsetfn };
90 
91 static struct builtin bintab[] = {
92     BUILTIN("ztie", 0, bin_ztie, 1, -1, 0, "d:f:r", NULL),
93     BUILTIN("zuntie", 0, bin_zuntie, 1, -1, 0, "u", NULL),
94     BUILTIN("zgdbmpath", 0, bin_zgdbmpath, 1, -1, 0, "", NULL),
95 };
96 
97 #define ROARRPARAMDEF(name, var) \
98     { name, PM_ARRAY | PM_READONLY, (void *) var, NULL,  NULL, NULL, NULL }
99 
100 /* Holds names of all tied parameters */
101 static char **zgdbm_tied;
102 
103 static struct paramdef patab[] = {
104     ROARRPARAMDEF( "zgdbm_tied", &zgdbm_tied ),
105 };
106 
107 /**/
108 static int
bin_ztie(char * nam,char ** args,Options ops,UNUSED (int func))109 bin_ztie(char *nam, char **args, Options ops, UNUSED(int func))
110 {
111     struct gsu_scalar_ext *dbf_carrier;
112     char *resource_name, *pmname;
113     GDBM_FILE dbf = NULL;
114     int read_write = GDBM_SYNC, pmflags = PM_REMOVABLE;
115     Param tied_param;
116 
117     if(!OPT_ISSET(ops,'d')) {
118         zwarnnam(nam, "you must pass `-d %s'", backtype);
119 	return 1;
120     }
121     if(!OPT_ISSET(ops,'f')) {
122         zwarnnam(nam, "you must pass `-f' with a filename", NULL);
123 	return 1;
124     }
125     if (OPT_ISSET(ops,'r')) {
126 	read_write |= GDBM_READER;
127 	pmflags |= PM_READONLY;
128     } else {
129 	read_write |= GDBM_WRCREAT;
130     }
131 
132     /* Here should be a lookup of the backend type against
133      * a registry, if generam DB mechanism is to be added */
134     if (strcmp(OPT_ARG(ops, 'd'), backtype) != 0) {
135         zwarnnam(nam, "unsupported backend type `%s'", OPT_ARG(ops, 'd'));
136 	return 1;
137     }
138 
139     resource_name = OPT_ARG(ops, 'f');
140     pmname = *args;
141 
142     if ((tied_param = (Param)paramtab->getnode(paramtab, pmname)) &&
143 	!(tied_param->node.flags & PM_UNSET)) {
144 	/*
145 	 * Unset any existing parameter.  Note there's no implicit
146 	 * "local" here, but if the existing parameter is local
147 	 * then new parameter will be also local without following
148          * unset.
149 	 *
150 	 * We need to do this before attempting to open the DB
151 	 * in case this variable is already tied to a DB.
152 	 *
153 	 * This can fail if the variable is readonly or restricted.
154 	 * We could call unsetparam() and check errflag instead
155 	 * of the return status.
156 	 */
157 	if (unsetparam_pm(tied_param, 0, 1))
158 	    return 1;
159     }
160 
161     gdbm_errno=0;
162     dbf = gdbm_open(resource_name, 0, read_write, 0666, 0);
163     if(dbf == NULL) {
164 	zwarnnam(nam, "error opening database file %s (%s)", resource_name, gdbm_strerror(gdbm_errno));
165 	return 1;
166     }
167 
168     if (!(tied_param = createhash(pmname, pmflags))) {
169         zwarnnam(nam, "cannot create the requested parameter %s", pmname);
170 	gdbm_close(dbf);
171 	return 1;
172     }
173 
174     tied_param->gsu.h = &gdbm_hash_gsu;
175 
176     /* Allocate parameter sub-gsu, fill dbf field.
177      * dbf allocation is 1 to 1 accompanied by
178      * gsu_scalar_ext allocation. */
179 
180     dbf_carrier = (struct gsu_scalar_ext *) zalloc(sizeof(struct gsu_scalar_ext));
181     dbf_carrier->std = gdbm_gsu_ext.std;
182     dbf_carrier->dbf = dbf;
183     tied_param->u.hash->tmpdata = (void *)dbf_carrier;
184 
185     /* Fill also file path field */
186     if (*resource_name != '/') {
187         /* Code copied from check_autoload() */
188         resource_name = zhtricat(metafy(zgetcwd(), -1, META_HEAPDUP), "/", resource_name);
189         resource_name = xsymlink(resource_name, 1);
190     }
191     dbf_carrier->dbfile_path = ztrdup(resource_name);
192 
193     addmodulefd(gdbm_fdesc(dbf), FDT_INTERNAL);
194     append_tied_name(pmname);
195 
196     return 0;
197 }
198 
199 /**/
200 static int
bin_zuntie(char * nam,char ** args,Options ops,UNUSED (int func))201 bin_zuntie(char *nam, char **args, Options ops, UNUSED(int func))
202 {
203     Param pm;
204     char *pmname;
205     int ret = 0;
206 
207     for (pmname = *args; *args++; pmname = *args) {
208 	pm = (Param) paramtab->getnode(paramtab, pmname);
209 	if(!pm) {
210 	    zwarnnam(nam, "cannot untie %s", pmname);
211 	    ret = 1;
212 	    continue;
213 	}
214 	if (pm->gsu.h != &gdbm_hash_gsu) {
215 	    zwarnnam(nam, "not a tied gdbm hash: %s", pmname);
216 	    ret = 1;
217 	    continue;
218 	}
219 
220 	queue_signals();
221 	if (OPT_ISSET(ops,'u')) {
222             pm->node.flags &= ~PM_READONLY;
223         }
224 	if (unsetparam_pm(pm, 0, 1)) {
225 	    /* assume already reported */
226 	    ret = 1;
227 	}
228 	unqueue_signals();
229     }
230 
231     return ret;
232 }
233 
234 /**/
235 static int
bin_zgdbmpath(char * nam,char ** args,Options ops,UNUSED (int func))236 bin_zgdbmpath(char *nam, char **args, Options ops, UNUSED(int func))
237 {
238     Param pm;
239     char *pmname;
240 
241     pmname = *args;
242 
243     if (!pmname) {
244         zwarnnam(nam, "parameter name (whose path is to be written to $REPLY) is required");
245         return 1;
246     }
247 
248     pm = (Param) paramtab->getnode(paramtab, pmname);
249     if(!pm) {
250         zwarnnam(nam, "no such parameter: %s", pmname);
251         return 1;
252     }
253 
254     if (pm->gsu.h != &gdbm_hash_gsu) {
255         zwarnnam(nam, "not a tied gdbm parameter: %s", pmname);
256         return 1;
257     }
258 
259     /* Paranoia, it *will* be always set */
260     if (((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbfile_path) {
261         setsparam("REPLY", ztrdup(((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbfile_path));
262     } else {
263         setsparam("REPLY", ztrdup(""));
264     }
265 
266     return 0;
267 }
268 
269 /*
270  * The param is actual param in hash – always, because
271  * getgdbmnode creates every new key seen. However, it
272  * might be not PM_UPTODATE - which means that database
273  * wasn't yet queried.
274  *
275  * It will be left in this state if database doesn't
276  * contain such key. That might be a drawback, maybe
277  * setting to empty value has sense.
278  */
279 
280 /**/
281 static char *
gdbmgetfn(Param pm)282 gdbmgetfn(Param pm)
283 {
284     datum key, content;
285     int ret, umlen;
286     char *umkey;
287     GDBM_FILE dbf;
288 
289     /* Key already retrieved? There is no sense of asking the
290      * database again, because:
291      * - there can be only multiple readers
292      * - so, no writer + reader use is allowed
293      *
294      * Thus:
295      * - if we are writers, we for sure have newest copy of data
296      * - if we are readers, we for sure have newest copy of data
297      */
298     if ( pm->node.flags & PM_UPTODATE ) {
299         return pm->u.str ? pm->u.str : "";
300     }
301 
302     /* Unmetafy key. GDBM fits nice into this
303      * process, as it uses length of data */
304     umlen = 0;
305     umkey = unmetafy_zalloc(pm->node.nam,&umlen);
306 
307     key.dptr = umkey;
308     key.dsize = umlen;
309 
310     dbf = ((struct gsu_scalar_ext *)pm->gsu.s)->dbf;
311 
312     if((ret = gdbm_exists(dbf, key))) {
313         /* We have data – store it, return it */
314         pm->node.flags |= PM_UPTODATE;
315 
316         content = gdbm_fetch(dbf, key);
317 
318         /* Ensure there's no leak */
319         if (pm->u.str) {
320             zsfree(pm->u.str);
321             pm->u.str = NULL;
322         }
323 
324         /* Metafy returned data. All fits - metafy
325          * can obtain data length to avoid using \0 */
326         pm->u.str = metafy(content.dptr, content.dsize, META_DUP);
327         /* gdbm allocates with malloc */
328         free(content.dptr);
329 
330         /* Free key */
331         zfree(umkey, umlen+1);
332 
333         /* Can return pointer, correctly saved inside hash */
334         return pm->u.str;
335     }
336 
337     /* Free key */
338     zfree(umkey, umlen+1);
339 
340     return "";
341 }
342 
343 /**/
344 static void
gdbmsetfn(Param pm,char * val)345 gdbmsetfn(Param pm, char *val)
346 {
347     datum key, content;
348     GDBM_FILE dbf;
349 
350     /* Set is done on parameter and on database.
351      * See the allowed workers / readers comment
352      * at gdbmgetfn() */
353 
354     /* Parameter */
355     if (pm->u.str) {
356         zsfree(pm->u.str);
357         pm->u.str = NULL;
358         pm->node.flags &= ~(PM_UPTODATE);
359     }
360 
361     if (val) {
362         pm->u.str = val;
363         pm->node.flags |= PM_UPTODATE;
364     }
365 
366     /* Database */
367     dbf = ((struct gsu_scalar_ext *)pm->gsu.s)->dbf;
368     if (dbf && no_database_action == 0) {
369         int umlen = 0;
370         char *umkey = unmetafy_zalloc(pm->node.nam,&umlen);
371 
372         key.dptr = umkey;
373         key.dsize = umlen;
374 
375         if (val) {
376             /* Unmetafy with exact zalloc size */
377             char *umval = unmetafy_zalloc(val,&umlen);
378 
379             /* Store */
380             content.dptr = umval;
381             content.dsize = umlen;
382             (void)gdbm_store(dbf, key, content, GDBM_REPLACE);
383 
384             /* Free */
385             zfree(umval, umlen+1);
386         } else {
387             (void)gdbm_delete(dbf, key);
388         }
389 
390         /* Free key */
391         zfree(umkey, key.dsize+1);
392     }
393 }
394 
395 /**/
396 static void
gdbmunsetfn(Param pm,UNUSED (int um))397 gdbmunsetfn(Param pm, UNUSED(int um))
398 {
399     /* Set with NULL */
400     gdbmsetfn(pm, NULL);
401 }
402 
403 /**/
404 static HashNode
getgdbmnode(HashTable ht,const char * name)405 getgdbmnode(HashTable ht, const char *name)
406 {
407     HashNode hn = gethashnode2( ht, name );
408     Param val_pm = (Param) hn;
409 
410     /* Entry for key doesn't exist? Create it now,
411      * it will be interfacing between the database
412      * and Zsh - through special gdbm_gsu. So, any
413      * seen key results in new interfacing parameter.
414      *
415      * Previous code was returning heap arena Param
416      * that wasn't actually added to the hash. It was
417      * plainly name / database-key holder. Here we
418      * add the Param to its hash, it is not PM_UPTODATE.
419      * It will be loaded from database *and filled*
420      * or left in that state if the database doesn't
421      * contain it.
422      *
423      * No heap arena memory is used, memory usage is
424      * now limited - by number of distinct keys seen,
425      * not by number of key *uses*.
426      * */
427 
428     if ( ! val_pm ) {
429         val_pm = (Param) zshcalloc( sizeof (*val_pm) );
430         val_pm->node.flags = PM_SCALAR | PM_HASHELEM; /* no PM_UPTODATE */
431         val_pm->gsu.s = (GsuScalar) ht->tmpdata;
432         ht->addnode( ht, ztrdup( name ), val_pm ); /* sets pm->node.nam */
433     }
434 
435     return (HashNode) val_pm;
436 }
437 
438 /**/
439 static void
scangdbmkeys(HashTable ht,ScanFunc func,int flags)440 scangdbmkeys(HashTable ht, ScanFunc func, int flags)
441 {
442     datum key, prev_key;
443     GDBM_FILE dbf = ((struct gsu_scalar_ext *)ht->tmpdata)->dbf;
444 
445     /* Iterate keys adding them to hash, so
446      * we have Param to use in `func` */
447     key = gdbm_firstkey(dbf);
448 
449     while(key.dptr) {
450         /* This returns database-interfacing Param,
451          * it will return u.str or first fetch data
452          * if not PM_UPTODATE (newly created) */
453         char *zkey = metafy(key.dptr, key.dsize, META_DUP);
454         HashNode hn = getgdbmnode(ht, zkey);
455         zsfree( zkey );
456 
457 	func(hn, flags);
458 
459         /* Iterate - no problem as interfacing Param
460          * will do at most only fetches, not stores */
461         prev_key = key;
462         key = gdbm_nextkey(dbf, key);
463         free(prev_key.dptr);
464     }
465 
466 }
467 
468 /*
469  * Replace database with new hash
470  */
471 
472 /**/
473 static void
gdbmhashsetfn(Param pm,HashTable ht)474 gdbmhashsetfn(Param pm, HashTable ht)
475 {
476     int i;
477     HashNode hn;
478     GDBM_FILE dbf;
479     datum key, content;
480 
481     if (!pm->u.hash || pm->u.hash == ht)
482 	return;
483 
484     if (!(dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf))
485 	return;
486 
487     key = gdbm_firstkey(dbf);
488     while (key.dptr) {
489 	queue_signals();
490 	(void)gdbm_delete(dbf, key);
491 	free(key.dptr);
492 	unqueue_signals();
493 	key = gdbm_firstkey(dbf);
494     }
495 
496     /* Just deleted everything, clean up if no new data.
497      * User can also reorganize via gdbmtool. */
498     if (!ht || ht->hsize == 0) {
499         (void)gdbm_reorganize(dbf);
500     }
501 
502     no_database_action = 1;
503     emptyhashtable(pm->u.hash);
504     no_database_action = 0;
505 
506     if (!ht)
507 	return;
508 
509      /* Put new strings into database, waiting
510       * for their interfacing-Params to be created */
511 
512     for (i = 0; i < ht->hsize; i++) {
513 	for (hn = ht->nodes[i]; hn; hn = hn->next) {
514 	    struct value v;
515             int umlen = 0;
516             char *umkey, *umval;
517 
518 	    v.isarr = v.flags = v.start = 0;
519 	    v.end = -1;
520 	    v.arr = NULL;
521 	    v.pm = (Param) hn;
522 
523             /* Unmetafy key */
524             umkey = unmetafy_zalloc(v.pm->node.nam,&umlen);
525 
526 	    key.dptr = umkey;
527 	    key.dsize = umlen;
528 
529 	    queue_signals();
530 
531             /* Unmetafy */
532             umval = unmetafy_zalloc(getstrvalue(&v),&umlen);
533 
534             /* Store */
535 	    content.dptr = umval;
536 	    content.dsize = umlen;
537 	    (void)gdbm_store(dbf, key, content, GDBM_REPLACE);
538 
539             /* Free - unmetafy_zalloc allocates
540              * exact required space + 1 null byte */
541             zfree(umval, content.dsize+1);
542             zfree(umkey, key.dsize+1);
543 
544 	    unqueue_signals();
545 	}
546     }
547     /* We reuse our hash, the input is to be deleted */
548     deleteparamtable(ht);
549 }
550 
551 /**/
552 static void
gdbmuntie(Param pm)553 gdbmuntie(Param pm)
554 {
555     GDBM_FILE dbf = ((struct gsu_scalar_ext *)pm->u.hash->tmpdata)->dbf;
556     HashTable ht = pm->u.hash;
557 
558     if (dbf) { /* paranoia */
559 	fdtable[gdbm_fdesc(dbf)] = FDT_UNUSED;
560         gdbm_close(dbf);
561 
562         /* Let hash fields know there's no backend */
563         ((struct gsu_scalar_ext *)ht->tmpdata)->dbf = NULL;
564 
565         /* Remove from list of tied parameters */
566         remove_tied_name(pm->node.nam);
567     }
568 
569     /* for completeness ... createspecialhash() should have an inverse */
570     ht->getnode = ht->getnode2 = gethashnode2;
571     ht->scantab = NULL;
572 
573     pm->node.flags &= ~(PM_SPECIAL|PM_READONLY);
574     pm->gsu.h = &stdhash_gsu;
575 }
576 
577 /**/
578 static void
gdbmhashunsetfn(Param pm,UNUSED (int exp))579 gdbmhashunsetfn(Param pm, UNUSED(int exp))
580 {
581     struct gsu_scalar_ext * gsu_ext;
582 
583     gdbmuntie(pm);
584 
585     /* Remember custom GSU structure assigned to
586      * u.hash->tmpdata before hash gets deleted */
587     gsu_ext = pm->u.hash->tmpdata;
588 
589     /* Uses normal unsetter (because gdbmuntie is called above).
590      * Will delete all owned field-parameters and also hashtable. */
591     pm->gsu.h->setfn(pm, NULL);
592 
593     /* Don't need custom GSU structure with its
594      * GDBM_FILE pointer anymore */
595     zsfree( gsu_ext->dbfile_path );
596     zfree( gsu_ext, sizeof(struct gsu_scalar_ext));
597 
598     pm->node.flags |= PM_UNSET;
599 }
600 
601 static struct features module_features = {
602     bintab, sizeof(bintab)/sizeof(*bintab),
603     NULL, 0,
604     NULL, 0,
605     patab, sizeof(patab)/sizeof(*patab),
606     0
607 };
608 
609 /**/
610 int
setup_(UNUSED (Module m))611 setup_(UNUSED(Module m))
612 {
613     return 0;
614 }
615 
616 /**/
617 int
features_(Module m,char *** features)618 features_(Module m, char ***features)
619 {
620     *features = featuresarray(m, &module_features);
621     return 0;
622 }
623 
624 /**/
625 int
enables_(Module m,int ** enables)626 enables_(Module m, int **enables)
627 {
628     return handlefeatures(m, &module_features, enables);
629 }
630 
631 /**/
632 int
boot_(UNUSED (Module m))633 boot_(UNUSED(Module m))
634 {
635     zgdbm_tied = zshcalloc((1) * sizeof(char *));
636     return 0;
637 }
638 
639 /**/
640 int
cleanup_(Module m)641 cleanup_(Module m)
642 {
643     /* This frees `zgdbm_tied` */
644     return setfeatureenables(m, &module_features, NULL);
645 }
646 
647 /**/
648 int
finish_(UNUSED (Module m))649 finish_(UNUSED(Module m))
650 {
651     return 0;
652 }
653 
654 /*********************
655  * Utility functions *
656  *********************/
657 
createhash(char * name,int flags)658 static Param createhash( char *name, int flags ) {
659     Param pm;
660     HashTable ht;
661 
662     pm = createparam(name, flags | PM_SPECIAL | PM_HASHED);
663     if (!pm) {
664         return NULL;
665     }
666 
667     if (pm->old)
668 	pm->level = locallevel;
669 
670     /* This creates standard hash. */
671     ht = pm->u.hash = newparamtable(17, name);
672     if (!pm->u.hash) {
673         paramtab->removenode(paramtab, name);
674         paramtab->freenode(&pm->node);
675         zwarnnam(name, "out of memory when allocating hash");
676         return NULL;
677     }
678 
679     /* Does free Param (unsetfn is called) */
680     ht->freenode = myfreeparamnode;
681 
682     /* These provide special features */
683     ht->getnode = ht->getnode2 = getgdbmnode;
684     ht->scantab = scangdbmkeys;
685 
686     return pm;
687 }
688 
689 /*
690  * Adds parameter name to `zgdbm_tied`
691  */
692 
append_tied_name(const char * name)693 static int append_tied_name( const char *name ) {
694     int old_len = arrlen(zgdbm_tied);
695     char **new_zgdbm_tied = zshcalloc( (old_len+2) * sizeof(char *));
696 
697     /* Copy */
698     char **p = zgdbm_tied;
699     char **dst = new_zgdbm_tied;
700     while (*p) {
701         *dst++ = *p++;
702     }
703 
704     /* Append new one */
705     *dst = ztrdup(name);
706 
707     /* Substitute, free old one */
708     zfree(zgdbm_tied, sizeof(char *) * (old_len + 1));
709     zgdbm_tied = new_zgdbm_tied;
710 
711     return 0;
712 }
713 
714 /*
715  * Removes parameter name from `zgdbm_tied`
716  */
717 
remove_tied_name(const char * name)718 static int remove_tied_name( const char *name ) {
719     int old_len = arrlen(zgdbm_tied);
720     int new_len;
721 
722     /* Two stage, to always have arrlen() == zfree-size - 1.
723      * Could do allocation and revert when `not found`, but
724      * what would be better about that. */
725 
726     /* Find one to remove */
727     char **p = zgdbm_tied;
728     while (*p) {
729         if (0==strcmp(name,*p)) {
730             break;
731         }
732         p++;
733     }
734 
735     if (*p)
736 	zsfree(*p);
737 
738     /* Copy x+1 to x */
739     while (*p) {
740         *p=*(p+1);
741         p++;
742     }
743 
744     /* Second stage. Size changed? Only old_size-1
745      * change is possible, but.. paranoia way */
746     new_len = arrlen(zgdbm_tied);
747     if (new_len != old_len) {
748         char **dst;
749         char **new_zgdbm_tied = zshcalloc((new_len+1) * sizeof(char *));
750 
751         /* Copy */
752         p = zgdbm_tied;
753         dst = new_zgdbm_tied;
754         while (*p) {
755             *dst++ = *p++;
756         }
757         *dst = NULL;
758 
759         /* Substitute, free old one */
760         zfree(zgdbm_tied, sizeof(char *) * (old_len + 1));
761         zgdbm_tied = new_zgdbm_tied;
762     }
763 
764     return 0;
765 }
766 
767 /*
768  * Unmetafy that:
769  * - duplicates buffer to work on it,
770  * - does zalloc of exact size for the new string,
771  * - restores work buffer to original content, to restore strlen
772  */
773 static char *
unmetafy_zalloc(const char * to_copy,int * new_len)774 unmetafy_zalloc(const char *to_copy, int *new_len) {
775     char *work, *to_return;
776     int my_new_len = 0;
777 
778     work = ztrdup(to_copy);
779     work = unmetafy(work,&my_new_len);
780 
781     if (new_len)
782         *new_len = my_new_len;
783 
784     /* This string can be correctly zsfree()-d */
785     to_return = (char *) zalloc((my_new_len+1)*sizeof(char));
786     memcpy(to_return, work, sizeof(char)*my_new_len); /* memcpy handles $'\0' */
787     to_return[my_new_len]='\0';
788 
789     /* Restore original strlen and correctly free */
790     strcpy(work, to_copy);
791     zsfree(work);
792 
793     return to_return;
794 }
795 
796 static void
myfreeparamnode(HashNode hn)797 myfreeparamnode(HashNode hn)
798 {
799     Param pm = (Param) hn;
800 
801     /* Upstream: The second argument of unsetfn() is used by modules to
802      * differentiate "exp"licit unset from implicit unset, as when
803      * a parameter is going out of scope.  It's not clear which
804      * of these applies here, but passing 1 has always worked.
805      */
806 
807     /* if (delunset) */
808     pm->gsu.s->unsetfn(pm, 1);
809 
810     zsfree(pm->node.nam);
811     /* If this variable was tied by the user, ename was ztrdup'd */
812     if (!(pm->node.flags & PM_SPECIAL) && pm->ename) {
813         zsfree(pm->ename);
814         pm->ename = NULL;
815     }
816     zfree(pm, sizeof(struct param));
817 }
818 
819 #else
820 # error no gdbm
821 #endif /* have gdbm */
822