1 /* sdb - MIT - Copyright 2011-2021 - pancake */
2 
3 #include <stdio.h>
4 #include <fcntl.h>
5 #include <errno.h>
6 #include <string.h>
7 #include <stdlib.h>
8 #include <sys/stat.h>
9 #include "sdb.h"
10 
11 #if 0
12 static inline SdbKv *kv_at(HtPP *ht, HtPPBucket *bt, ut32 i) {
13 	return (SdbKv *)((char *)bt->arr + i * ht->opt.elem_size);
14 }
15 
16 static inline SdbKv *prev_kv(HtPP *ht, SdbKv *kv) {
17 	return (SdbKv *)((char *)kv - ht->opt.elem_size);
18 }
19 #endif
20 
next_kv(HtPP * ht,SdbKv * kv)21 static inline SdbKv *next_kv(HtPP *ht, SdbKv *kv) {
22 	return (SdbKv *)((char *)kv + ht->opt.elem_size);
23 }
24 
25 #define BUCKET_FOREACH(ht, bt, j, kv)					\
26 	for ((j) = 0, (kv) = (SdbKv *)(bt)->arr; j < (bt)->count; (j)++, (kv) = next_kv (ht, kv))
27 
28 #define BUCKET_FOREACH_SAFE(ht, bt, j, count, kv)			\
29 	if ((bt)->arr)							\
30 		for ((j) = 0, (kv) = (SdbKv *)(bt)->arr, (count) = (ht)->count; \
31 		     (j) < (bt)->count;					\
32 		     (j) = (count) == (ht)->count? j + 1: j, (kv) = (count) == (ht)->count? next_kv (ht, kv): kv, (count) = (ht)->count)
33 
nextcas(void)34 static inline int nextcas(void) {
35 	static ut32 cas = 1;
36 	if (!cas) {
37 		cas++;
38 	}
39 	return cas++;
40 }
41 
42 static SdbHook global_hook = NULL;
43 static void* global_user = NULL;
44 
sdb_global_hook(SdbHook hook,void * user)45 SDB_API void sdb_global_hook(SdbHook hook, void *user) {
46 	global_hook = hook;
47 	global_user = user;
48 }
49 
50 // TODO: use mmap instead of read.. much faster!
sdb_new0(void)51 SDB_API Sdb* sdb_new0(void) {
52 	return sdb_new (NULL, NULL, 0);
53 }
54 
sdb_new(const char * path,const char * name,int lock)55 SDB_API Sdb* sdb_new(const char *path, const char *name, int lock) {
56 	Sdb* s = R_NEW0 (Sdb);
57 	if (!s) {
58 		return NULL;
59 	}
60 	s->db.fd = -1;
61 	s->fd = -1;
62 	s->refs = 1;
63 	s->ht = sdb_ht_new ();
64 	if (path && !*path) {
65 		path = NULL;
66 	}
67 	if (name && *name && strcmp (name, "-")) {
68 		if (path && *path) {
69 			size_t plen = strlen (path);
70 			size_t nlen = strlen (name);
71 			s->dir = malloc (plen + nlen + 2);
72 			if (!s->dir) {
73 				free (s);
74 				return NULL;
75 			}
76 			memcpy (s->dir, path, plen);
77 			s->dir[plen] = '/';
78 			memcpy (s->dir + plen + 1, name, nlen + 1);
79 			s->path = strdup (path);
80 		} else {
81 			s->dir = strdup (name);
82 		}
83 		switch (lock) {
84 		case 1:
85 			if (!sdb_lock (sdb_lock_file (s->dir))) {
86 				goto fail;
87 			}
88 			break;
89 		case 2:
90 			if (!sdb_lock_wait (sdb_lock_file (s->dir))) {
91 				goto fail;
92 			}
93 			break;
94 		}
95 		if (sdb_open (s, s->dir) == -1) {
96 			s->last = s->timestamped? sdb_now (): 0LL;
97 			// TODO: must fail if we cant open for write in sync
98 		}
99 		s->name = strdup (name);
100 	} else {
101 		s->last = s->timestamped? sdb_now (): 0LL;
102 		s->fd = -1;
103 	}
104 	s->journal = -1;
105 	s->fdump = -1;
106 	s->depth = 0;
107 	s->ndump = NULL;
108 	s->ns = ls_new (); // TODO: should be NULL
109 	if (!s->ns) {
110 		goto fail;
111 	}
112 	s->ns->free = NULL;
113 	if (!s->ns) {
114 		goto fail;
115 	}
116 	s->lock = lock;
117 	// if open fails ignore
118 	if (global_hook) {
119 		sdb_hook (s, global_hook, global_user);
120 	}
121 	cdb_init (&s->db, s->fd);
122 	return s;
123 fail:
124 	if (s->fd != -1) {
125 		close (s->fd);
126 		s->fd = -1;
127 	}
128 	free (s->dir);
129 	free (s->name);
130 	free (s->path);
131 	free (s);
132 	return NULL;
133 }
134 
135 // XXX: this is wrong. stuff not stored in memory is lost
sdb_file(Sdb * s,const char * dir)136 SDB_API void sdb_file(Sdb* s, const char *dir) {
137 	if (s->lock) {
138 		sdb_unlock (sdb_lock_file (s->dir));
139 	}
140 	free (s->dir);
141 	s->dir = (dir && *dir)? strdup (dir): NULL;
142 	if (s->lock) {
143 		sdb_lock (sdb_lock_file (s->dir));
144 	}
145 }
146 
sdb_merge_cb(void * user,const char * k,const char * v)147 static bool sdb_merge_cb(void *user, const char *k, const char *v) {
148 	sdb_set (user, k, v, 0);
149 	return true;
150 }
151 
sdb_merge(Sdb * d,Sdb * s)152 SDB_API bool sdb_merge(Sdb* d, Sdb *s) {
153 	return sdb_foreach (s, sdb_merge_cb, d);
154 }
155 
sdb_isempty(Sdb * s)156 SDB_API bool sdb_isempty(Sdb *s) {
157 	if (s) {
158 		if (s->db.fd != -1) {
159 			sdb_dump_begin (s);
160 			while (sdb_dump_hasnext (s)) {
161 				return false;
162 			}
163 		}
164 		if (s->ht && s->ht->count > 0) {
165 			return false;
166 		}
167 	}
168 	return true;
169 }
170 
sdb_count(Sdb * s)171 SDB_API int sdb_count(Sdb *s) {
172 	int count = 0;
173 	if (s) {
174 		if (s->db.fd != -1) {
175 			sdb_dump_begin (s);
176 			while (sdb_dump_hasnext (s)) {
177 				count++;
178 			}
179 		}
180 		if (s->ht) {
181 			count += s->ht->count;
182 		}
183 	}
184 	return count;
185 }
186 
sdb_fini(Sdb * s,bool donull)187 static void sdb_fini(Sdb* s, bool donull) {
188 	if (!s) {
189 		return;
190 	}
191 	sdb_hook_free (s);
192 	cdb_free (&s->db);
193 	if (s->lock) {
194 		sdb_unlock (sdb_lock_file (s->dir));
195 	}
196 	sdb_ns_free (s);
197 	s->refs = 0;
198 	free (s->name);
199 	free (s->path);
200 	ls_free (s->ns);
201 	sdb_ht_free (s->ht);
202 	sdb_journal_close (s);
203 	if (s->fd != -1) {
204 		close (s->fd);
205 		s->fd = -1;
206 	}
207 	free (s->ndump);
208 	free (s->dir);
209 	free (sdbkv_value (&s->tmpkv));
210 	s->tmpkv.base.value_len = 0;
211 	if (donull) {
212 		memset (s, 0, sizeof (Sdb));
213 	}
214 }
215 
sdb_free(Sdb * s)216 SDB_API bool sdb_free(Sdb* s) {
217 	if (s && s->ht && s->refs) {
218 		s->refs--;
219 		if (s->refs < 1) {
220 			s->refs = 0;
221 			sdb_fini (s, false);
222 			s->ht = NULL;
223 			free (s);
224 			return true;
225 		}
226 	}
227 	return false;
228 }
229 
sdb_const_get_len(Sdb * s,const char * key,int * vlen,ut32 * cas)230 SDB_API const char *sdb_const_get_len(Sdb* s, const char *key, int *vlen, ut32 *cas) {
231 	ut32 pos, len;
232 	ut64 now = 0LL;
233 	bool found;
234 
235 	if (cas) {
236 		*cas = 0;
237 	}
238 	if (vlen) {
239 		*vlen = 0;
240 	}
241 	if (!s || !key) {
242 		return NULL;
243 	}
244 	// TODO: optimize, iterate once
245 	size_t keylen = strlen (key);
246 
247 	/* search in memory */
248 	SdbKv *kv = (SdbKv*) sdb_ht_find_kvp (s->ht, key, &found);
249 	if (found) {
250 		if (!sdbkv_value (kv) || !*sdbkv_value (kv)) {
251 			return NULL;
252 		}
253 		if (s->timestamped && kv->expire) {
254 			if (!now) {
255 				now = sdb_now ();
256 			}
257 			if (now > kv->expire) {
258 				sdb_unset (s, key, 0);
259 				return NULL;
260 			}
261 		}
262 		if (cas) {
263 			*cas = kv->cas;
264 		}
265 		if (vlen) {
266 			*vlen = sdbkv_value_len (kv);
267 		}
268 		return sdbkv_value (kv);
269 	}
270 	/* search in disk */
271 	if (s->fd == -1) {
272 		return NULL;
273 	}
274 	(void) cdb_findstart (&s->db);
275 	if (cdb_findnext (&s->db, s->ht->opt.hashfn (key), key, keylen) < 1) {
276 		return NULL;
277 	}
278 	len = cdb_datalen (&s->db);
279 	if (len < SDB_MIN_VALUE || len >= SDB_MAX_VALUE) {
280 		return NULL;
281 	}
282 	if (vlen) {
283 		*vlen = len;
284 	}
285 	pos = cdb_datapos (&s->db);
286 	return s->db.map + pos;
287 }
288 
sdb_const_get(Sdb * s,const char * key,ut32 * cas)289 SDB_API const char *sdb_const_get(Sdb* s, const char *key, ut32 *cas) {
290 	return sdb_const_get_len (s, key, NULL, cas);
291 }
292 
293 // TODO: add sdb_getf?
294 
sdb_get_len(Sdb * s,const char * key,int * vlen,ut32 * cas)295 SDB_API char *sdb_get_len(Sdb* s, const char *key, int *vlen, ut32 *cas) {
296 	const char *value = sdb_const_get_len (s, key, vlen, cas);
297 	return value ? strdup (value) : NULL;
298 }
299 
sdb_get(Sdb * s,const char * key,ut32 * cas)300 SDB_API char *sdb_get(Sdb* s, const char *key, ut32 *cas) {
301 	return sdb_get_len (s, key, NULL, cas);
302 }
303 
sdb_unset(Sdb * s,const char * key,ut32 cas)304 SDB_API int sdb_unset(Sdb* s, const char *key, ut32 cas) {
305 	return key? sdb_set (s, key, "", cas): 0;
306 }
307 
308 /* remove from memory */
sdb_remove(Sdb * s,const char * key,ut32 cas)309 SDB_API bool sdb_remove(Sdb *s, const char *key, ut32 cas) {
310 	return sdb_ht_delete (s->ht, key);
311 }
312 
313 // alias for '-key=str'.. '+key=str' concats
sdb_uncat(Sdb * s,const char * key,const char * value,ut32 cas)314 SDB_API int sdb_uncat(Sdb *s, const char *key, const char *value, ut32 cas) {
315 	// remove 'value' from current key value.
316 	// TODO: cas is ignored here
317 	int vlen = 0, valen;
318 	char *p, *v = sdb_get_len (s, key, &vlen, NULL);
319 	int mod = 0;
320 	if (!v || !key || !value) {
321 		free (v);
322 		return 0;
323 	}
324 	valen = strlen (value);
325 	if (valen > 0) {
326 		while ((p = strstr (v, value))) {
327 			memmove (p, p + valen, strlen (p + valen) + 1);
328 			mod = 1;
329 		}
330 	}
331 	if (mod) {
332 		sdb_set_owned (s, key, v, 0);
333 	} else {
334 		free (v);
335 	}
336 	return 0;
337 }
338 
sdb_concat(Sdb * s,const char * key,const char * value,ut32 cas)339 SDB_API int sdb_concat(Sdb *s, const char *key, const char *value, ut32 cas) {
340 	int kl, vl;
341 	const char *p;
342 	char *o;
343 	if (!s || !key || !*key || !value || !*value) {
344 		return 0;
345 	}
346 	p = sdb_const_get_len (s, key, &kl, 0);
347 	if (!p) {
348 		return sdb_set (s, key, value, cas);
349 	}
350 	vl = strlen (value);
351 	o = malloc (kl + vl + 1);
352 	if (o) {
353 		memcpy (o, p, kl);
354 		memcpy (o + kl, value, vl + 1);
355 		return sdb_set_owned (s, key, o, cas);
356 	}
357 	return 0;
358 }
359 
360 // set if not defined
sdb_add(Sdb * s,const char * key,const char * val,ut32 cas)361 SDB_API int sdb_add(Sdb* s, const char *key, const char *val, ut32 cas) {
362 	if (sdb_exists (s, key)) {
363 		return 0;
364 	}
365 	return sdb_set (s, key, val, cas);
366 }
367 
sdb_exists(Sdb * s,const char * key)368 SDB_API bool sdb_exists(Sdb* s, const char *key) {
369 	ut32 pos;
370 	char ch;
371 	bool found;
372 	size_t klen = strlen (key) + 1;
373 	if (!s) {
374 		return false;
375 	}
376 	SdbKv *kv = (SdbKv*)sdb_ht_find_kvp (s->ht, key, &found);
377 	if (found && kv) {
378 		char *v = sdbkv_value (kv);
379 		return v && *v;
380 	}
381 	if (s->fd == -1) {
382 		return false;
383 	}
384 	(void)cdb_findstart (&s->db);
385 	if (cdb_findnext (&s->db, sdb_hash (key), key, klen)) {
386 		pos = cdb_datapos (&s->db);
387 		cdb_read (&s->db, &ch, 1, pos);
388 		return ch != 0;
389 	}
390 	return false;
391 }
392 
sdb_open(Sdb * s,const char * file)393 SDB_API int sdb_open(Sdb *s, const char *file) {
394         struct stat st;
395 	if (!s) {
396 		return -1;
397 	}
398 	if (file) {
399 		if (sdb_text_check (s, file)) {
400 			return sdb_text_load (s, file);
401 		}
402 		if (s->fd != -1) {
403 			close (s->fd);
404 			s->fd = -1;
405 		}
406 		s->fd = open (file, O_RDONLY | O_BINARY);
407 		if (file != s->dir) {
408 			free (s->dir);
409 			s->dir = strdup (file);
410 			s->path = NULL; // TODO: path is important
411 		}
412 	}
413 	s->last = 0LL;
414 	if (s->fd != -1 && fstat (s->fd, &st) != -1) {
415 		if ((S_IFREG & st.st_mode) != S_IFREG) {
416 			eprintf ("Database must be a file\n");
417 			close (s->fd);
418 			s->fd = -1;
419 			return -1;
420 		}
421 		s->last = st.st_mtime;
422 	}
423 	if (s->fd != -1) {
424 		cdb_init (&s->db, s->fd);
425 	}
426 	return s->fd;
427 }
428 
sdb_close(Sdb * s)429 SDB_API void sdb_close(Sdb *s) {
430 	if (s) {
431 		if (s->fd != -1) {
432 			if (s->db.fd != -1 && s->db.fd == s->fd) {
433 				/* close db fd as well */
434 				s->db.fd = -1;
435 			}
436 			close (s->fd);
437 			s->fd = -1;
438 		}
439 		if (s->dir) {
440 			free (s->dir);
441 			s->dir = NULL;
442 		}
443 	}
444 }
445 
sdb_reset(Sdb * s)446 SDB_API void sdb_reset(Sdb* s) {
447 	if (!s) {
448 		return;
449 	}
450 	/* ignore disk cache, file is not removed, but we will ignore
451 	 * its values when syncing again */
452 	sdb_close (s);
453 	/* empty memory hashtable */
454 	sdb_ht_free (s->ht);
455 	s->ht = sdb_ht_new ();
456 }
457 
lastChar(const char * str)458 static char lastChar(const char *str) {
459 	int len = strlen (str);
460 	return str[(len > 0)? len - 1: 0];
461 }
462 
match(const char * str,const char * expr)463 static bool match(const char *str, const char *expr) {
464 	bool startsWith = *expr == '^';
465 	bool endsWith = lastChar (expr) == '$';
466 	if (startsWith && endsWith) {
467 		return strlen (str) == strlen (expr) - 2 && \
468 			!strncmp (str, expr + 1, strlen (expr) - 2);
469 	}
470 	if (startsWith) {
471 		return !strncmp (str, expr + 1, strlen (expr) - 1);
472 	}
473 	if (endsWith) {
474 		int alen = strlen (str);
475 		int blen = strlen (expr) - 1;
476 		if (alen <= blen) {
477 			return false;
478 		}
479 		const char *a = str + strlen (str) - blen;
480 		return (!strncmp (a, expr, blen));
481 	}
482 	return strstr (str, expr);
483 }
484 
sdbkv_match(SdbKv * kv,const char * expr)485 SDB_API bool sdbkv_match(SdbKv *kv, const char *expr) {
486 	// TODO: add syntax to negate condition
487 	// TODO: add syntax to OR k/v instead of AND
488 	// [^]str[$]=[^]str[$]
489 	const char *eq = strchr (expr, '=');
490 	if (eq) {
491 		char *e = strdup (expr);
492 		char *ep = e + (eq - expr);
493 		*ep++ = 0;
494 		bool res = !*e || match (sdbkv_key (kv), e);
495 		bool res2 = !*ep || match (sdbkv_value (kv), ep);
496 		free (e);
497 		return res && res2;
498 	}
499 	return match (sdbkv_key (kv), expr);
500 }
501 
sdbkv_new(const char * k,const char * v)502 SDB_API SdbKv* sdbkv_new(const char *k, const char *v) {
503 	return sdbkv_new2 (k, strlen (k), v, strlen (v));
504 }
505 
sdbkv_new2(const char * k,int kl,const char * v,int vl)506 SDB_API SdbKv* sdbkv_new2(const char *k, int kl, const char *v, int vl) {
507 	SdbKv *kv;
508 	if (v) {
509 		if (vl >= SDB_VSZ) {
510 			return NULL;
511 		}
512 	} else {
513 		vl = 0;
514 	}
515 	if (kl >= SDB_KSZ) {
516 		return NULL;
517 	}
518 	kv = R_NEW0 (SdbKv);
519 	kv->base.key_len = kl;
520 	kv->base.key = malloc (kv->base.key_len + 1);
521 	if (!kv->base.key) {
522 		free (kv);
523 		return NULL;
524 	}
525 	memcpy (kv->base.key, k, kv->base.key_len + 1);
526 	kv->base.value_len = vl;
527 	if (vl) {
528 		kv->base.value = malloc (vl + 1);
529 		if (!kv->base.value) {
530 			free (kv->base.key);
531 			free (kv);
532 			return NULL;
533 		}
534 		memcpy (kv->base.value, v, vl + 1);
535 	} else {
536 		kv->base.value = NULL;
537 		kv->base.value_len = 0;
538 	}
539 	kv->cas = nextcas ();
540 	kv->expire = 0LL;
541 	return kv;
542 }
543 
sdbkv_free(SdbKv * kv)544 SDB_API void sdbkv_free(SdbKv *kv) {
545 	if (kv) {
546 		free (sdbkv_key (kv));
547 		free (sdbkv_value (kv));
548 		R_FREE (kv);
549 	}
550 }
551 
sdb_set_internal(Sdb * s,const char * key,char * val,bool owned,ut32 cas)552 static ut32 sdb_set_internal(Sdb* s, const char *key, char *val, bool owned, ut32 cas) {
553 	ut32 vlen, klen;
554 	SdbKv *kv;
555 	bool found;
556 	if (!s || !key) {
557 		return 0;
558 	}
559 	if (!val) {
560 		if (owned) {
561 			val = strdup ("");
562 		} else {
563 			val = "";
564 		}
565 	}
566 	// XXX strlen computed twice.. because of check_*()
567 	klen = strlen (key);
568 	vlen = strlen (val);
569 	if (klen >= SDB_KSZ || vlen >= SDB_VSZ) {
570 		if (owned) {
571 			free (val);
572 		}
573 		return 0;
574 	}
575 	if (s->journal != -1) {
576 		sdb_journal_log (s, key, val);
577 	}
578 	cdb_findstart (&s->db);
579 	kv = sdb_ht_find_kvp (s->ht, key, &found);
580 	if (found && sdbkv_value (kv)) {
581 		if (cdb_findnext (&s->db, sdb_hash (key), key, klen)) {
582 			if (cas && kv->cas != cas) {
583 				if (owned) {
584 					free (val);
585 				}
586 				return 0;
587 			}
588 			if (vlen == sdbkv_value_len (kv) && !strcmp (sdbkv_value (kv), val)) {
589 				sdb_hook_call (s, key, val);
590 				return kv->cas;
591 			}
592 			kv->cas = cas = nextcas ();
593 			if (owned) {
594 				kv->base.value_len = vlen;
595 				free (kv->base.value);
596 				kv->base.value = val; // owned
597 			} else {
598 				if ((ut32)vlen > kv->base.value_len) {
599 					free (kv->base.value);
600 					kv->base.value = malloc (vlen + 1);
601 				}
602 				memcpy (kv->base.value, val, vlen + 1);
603 				kv->base.value_len = vlen;
604 			}
605 		} else {
606 			sdb_ht_delete (s->ht, key);
607 		}
608 		sdb_hook_call (s, key, val);
609 		return cas;
610 	}
611 	// empty values are also stored
612 	// TODO store only the ones that are in the CDB
613 	if (owned) {
614 		kv = sdbkv_new2 (key, klen, NULL, 0);
615 		if (kv) {
616 			kv->base.value = val;
617 			kv->base.value_len = vlen;
618 		}
619 	} else {
620 		kv = sdbkv_new2 (key, klen, val, vlen);
621 	}
622 	if (kv) {
623 		ut32 cas = kv->cas = nextcas ();
624 		sdb_ht_insert_kvp (s->ht, kv, true /*update*/);
625 		free (kv);
626 		sdb_hook_call (s, key, val);
627 		return cas;
628 	}
629 // kv set failed, no need to callback	sdb_hook_call (s, key, val);
630 	return 0;
631 }
632 
sdb_set_owned(Sdb * s,const char * key,char * val,ut32 cas)633 SDB_API int sdb_set_owned(Sdb* s, const char *key, char *val, ut32 cas) {
634 	return sdb_set_internal (s, key, val, true, cas);
635 }
636 
sdb_set(Sdb * s,const char * key,const char * val,ut32 cas)637 SDB_API int sdb_set(Sdb* s, const char *key, const char *val, ut32 cas) {
638 	return sdb_set_internal (s, key, (char *)val, false, cas);
639 }
640 
sdb_foreach_list_cb(void * user,const char * k,const char * v)641 static bool sdb_foreach_list_cb(void *user, const char *k, const char *v) {
642 	SdbList *list = (SdbList *)user;
643 	SdbKv *kv = R_NEW0 (SdbKv);
644 	/* seems like some k/v are constructed in the stack and cant be used after returning */
645 	kv->base.key = strdup (k);
646 	kv->base.value = strdup (v);
647 	ls_append (list, kv);
648 	return 1;
649 }
650 
__cmp_asc(const void * a,const void * b)651 static int __cmp_asc(const void *a, const void *b) {
652 	const SdbKv *ka = a, *kb = b;
653 	return strcmp (sdbkv_key (ka), sdbkv_key (kb));
654 }
655 
sdb_foreach_list(Sdb * s,bool sorted)656 SDB_API SdbList *sdb_foreach_list(Sdb* s, bool sorted) {
657 	SdbList *list = ls_newf ((SdbListFree)sdbkv_free);
658 	sdb_foreach (s, sdb_foreach_list_cb, list);
659 	if (sorted) {
660 		ls_sort (list, __cmp_asc);
661 	}
662 	return list;
663 }
664 
665 struct foreach_list_filter_t {
666 	SdbForeachCallback filter;
667 	SdbList *list;
668 };
669 
sdb_foreach_list_filter_cb(void * user,const char * k,const char * v)670 static bool sdb_foreach_list_filter_cb(void *user, const char *k, const char *v) {
671 	struct foreach_list_filter_t *u = (struct foreach_list_filter_t *)user;
672 	SdbList *list = u->list;
673 	SdbKv *kv = NULL;
674 
675 	if (!u->filter || u->filter (NULL, k, v)) {
676 		kv = R_NEW0 (SdbKv);
677 		if (!kv) {
678 			goto err;
679 		}
680 		kv->base.key = strdup (k);
681 		kv->base.value = strdup (v);
682 		if (!kv->base.key || !kv->base.value) {
683 			goto err;
684 		}
685 		ls_append (list, kv);
686 	}
687 	return true;
688  err:
689 	sdbkv_free (kv);
690 	return false;
691 }
692 
sdb_foreach_list_filter(Sdb * s,SdbForeachCallback filter,bool sorted)693 SDB_API SdbList *sdb_foreach_list_filter(Sdb* s, SdbForeachCallback filter, bool sorted) {
694 	struct foreach_list_filter_t u;
695 	SdbList *list = ls_newf ((SdbListFree)sdbkv_free);
696 
697 	if (!list) {
698 		return NULL;
699 	}
700 	u.filter = filter;
701 	u.list = list;
702 	sdb_foreach (s, sdb_foreach_list_filter_cb, &u);
703 	if (sorted) {
704 		ls_sort (list, __cmp_asc);
705 	}
706 	return list;
707 }
708 
709 typedef struct {
710 	const char *expr;
711 	SdbList *list;
712 	bool single;
713 } _match_sdb_user;
714 
sdb_foreach_match_cb(void * user,const char * k,const char * v)715 static bool sdb_foreach_match_cb(void *user, const char *k, const char *v) {
716 	_match_sdb_user *o = (_match_sdb_user*)user;
717 	SdbKv tkv = { .base.key = (char*)k, .base.value = (char*)v };
718 	if (sdbkv_match (&tkv, o->expr)) {
719 		SdbKv *kv = R_NEW0 (SdbKv);
720 		kv->base.key = strdup (k);
721 		kv->base.value = strdup (v);
722 		ls_append (o->list, kv);
723 		if (o->single) {
724 			return false;
725 		}
726 	}
727 	return true;
728 }
729 
sdb_foreach_match(Sdb * s,const char * expr,bool single)730 SDB_API SdbList *sdb_foreach_match(Sdb* s, const char *expr, bool single) {
731 	SdbList *list = ls_newf ((SdbListFree)sdbkv_free);
732 	_match_sdb_user o = { expr, list, single };
733 	sdb_foreach (s, sdb_foreach_match_cb, &o);
734 	return list;
735 }
736 
getbytes(Sdb * s,char * b,int len)737 static int getbytes(Sdb *s, char *b, int len) {
738 	if (!cdb_read (&s->db, b, len, s->pos)) {
739 		return -1;
740 	}
741 	s->pos += len;
742 	return len;
743 }
744 
sdb_foreach_end(Sdb * s,bool result)745 static bool sdb_foreach_end(Sdb *s, bool result) {
746 	s->depth--;
747 	return result;
748 }
749 
sdb_foreach_cdb(Sdb * s,SdbForeachCallback cb,SdbForeachCallback cb2,void * user)750 static bool sdb_foreach_cdb(Sdb *s, SdbForeachCallback cb, SdbForeachCallback cb2, void *user) {
751 	char *v = NULL;
752 	char k[SDB_MAX_KEY] = {0};
753 	bool found;
754 	sdb_dump_begin (s);
755 	while (sdb_dump_dupnext (s, k, &v, NULL)) {
756 		SdbKv *kv = sdb_ht_find_kvp (s->ht, k, &found);
757 		if (found) {
758 			free (v);
759 			if (kv && sdbkv_key (kv) && sdbkv_value (kv)) {
760 				if (!cb (user, sdbkv_key (kv), sdbkv_value (kv))) {
761 					return false;
762 				}
763 				if (cb2 && !cb2 (user, k, sdbkv_value (kv))) {
764 					return false;
765 				}
766 			}
767 		} else {
768 			if (!cb (user, k, v)) {
769 				free (v);
770 				return false;
771 			}
772 			free (v);
773 		}
774 	}
775 	return true;
776 }
777 
sdb_foreach(Sdb * s,SdbForeachCallback cb,void * user)778 SDB_API bool sdb_foreach(Sdb* s, SdbForeachCallback cb, void *user) {
779 	if (!s) {
780 		return false;
781 	}
782 	s->depth++;
783 	bool result = sdb_foreach_cdb (s, cb, NULL, user);
784 	if (!result) {
785 		return sdb_foreach_end (s, false);
786 	}
787 
788 	ut32 i;
789 	for (i = 0; i < s->ht->size; ++i) {
790 		HtPPBucket *bt = &s->ht->table[i];
791 		SdbKv *kv;
792 		ut32 j, count;
793 
794 		BUCKET_FOREACH_SAFE (s->ht, bt, j, count, kv) {
795 			if (kv && sdbkv_value (kv) && *sdbkv_value (kv)) {
796 				if (!cb (user, sdbkv_key (kv), sdbkv_value (kv))) {
797 					return sdb_foreach_end (s, false);
798 				}
799 			}
800 		}
801 	}
802 	return sdb_foreach_end (s, true);
803 }
804 
_insert_into_disk(void * user,const char * key,const char * value)805 static bool _insert_into_disk(void *user, const char *key, const char *value) {
806 	Sdb *s = (Sdb *)user;
807 	if (s) {
808 		sdb_disk_insert (s, key, value);
809 		return true;
810 	}
811 	return false;
812 }
813 
_remove_afer_insert(void * user,const char * k,const char * v)814 static bool _remove_afer_insert(void *user, const char *k, const char *v) {
815 	Sdb *s = (Sdb *)user;
816 	if (s) {
817 		sdb_ht_delete (s->ht, k);
818 		return true;
819 	}
820 	return false;
821 }
822 
sdb_sync(Sdb * s)823 SDB_API bool sdb_sync(Sdb* s) {
824 	bool result;
825 	ut32 i;
826 
827 	if (!s || !sdb_disk_create (s)) {
828 		return false;
829 	}
830 	result = sdb_foreach_cdb (s, _insert_into_disk, _remove_afer_insert, s);
831 	if (!result) {
832 		return false;
833 	}
834 
835 	/* append new keyvalues */
836 	for (i = 0; i < s->ht->size; ++i) {
837 		HtPPBucket *bt = &s->ht->table[i];
838 		SdbKv *kv;
839 		ut32 j, count;
840 
841 		BUCKET_FOREACH_SAFE (s->ht, bt, j, count, kv) {
842 			if (sdbkv_key (kv) && sdbkv_value (kv) && *sdbkv_value (kv) && !kv->expire) {
843 				if (sdb_disk_insert (s, sdbkv_key (kv), sdbkv_value (kv))) {
844 					sdb_remove (s, sdbkv_key (kv), 0);
845 				}
846 			}
847 		}
848 	}
849 	sdb_disk_finish (s);
850 	sdb_journal_clear (s);
851 	// TODO: sdb_reset memory state?
852 	return true;
853 }
854 
sdb_dump_begin(Sdb * s)855 SDB_API void sdb_dump_begin(Sdb* s) {
856 	if (s->fd != -1) {
857 		s->pos = sizeof (((struct cdb_make *)0)->final);
858 		seek_set (s->fd, s->pos);
859 	} else {
860 		s->pos = 0;
861 	}
862 }
863 
sdb_dump_next(Sdb * s)864 SDB_API SdbKv *sdb_dump_next(Sdb* s) {
865 	char *v = NULL;
866 	char k[SDB_MAX_KEY] = {0};
867 	int vl = 0;
868 	// we dont need to malloc, because all values are null terminated in memory.
869 	if (!sdb_dump_dupnext (s, k, &v, &vl)) {
870 		return NULL;
871 	}
872 	vl--;
873 	snprintf (sdbkv_key (&s->tmpkv), SDB_KSZ, "%s", k);
874 	free (sdbkv_value (&s->tmpkv));
875 	s->tmpkv.base.value = v;
876 	s->tmpkv.base.value_len = vl;
877 	return &s->tmpkv;
878 }
879 
sdb_dump_hasnext(Sdb * s)880 SDB_API bool sdb_dump_hasnext(Sdb* s) {
881 	ut32 k, v;
882 	if (!cdb_getkvlen (&s->db, &k, &v, s->pos)) {
883 		return false;
884 	}
885 	if (k < 1 || v < 1) {
886 		return false;
887 	}
888 	s->pos += k + v + 4;
889 	return true;
890 }
891 
sdb_stats(Sdb * s,ut32 * disk,ut32 * mem)892 SDB_API bool sdb_stats(Sdb *s, ut32 *disk, ut32 *mem) {
893 	if (!s) {
894 		return false;
895 	}
896 	if (disk) {
897 		ut32 count = 0;
898 		if (s->fd != -1) {
899 			sdb_dump_begin (s);
900 			while (sdb_dump_hasnext (s)) {
901 				count ++;
902 			}
903 		}
904 		*disk = count;
905 	}
906 	if (mem) {
907 		*mem = s->ht->count;
908 	}
909 	return disk || mem;
910 }
911 
912 // TODO: make it static? internal api?
sdb_dump_dupnext(Sdb * s,char * key,char ** value,int * _vlen)913 SDB_API bool sdb_dump_dupnext(Sdb* s, char *key, char **value, int *_vlen) {
914 	ut32 vlen = 0, klen = 0;
915 	if (value) {
916 		*value = NULL;
917 	}
918 	if (_vlen) {
919 		*_vlen = 0;
920 	}
921 	if (!cdb_getkvlen (&s->db, &klen, &vlen, s->pos)) {
922 		return false;
923 	}
924 	s->pos += 4;
925 	if (klen < 1 || vlen < 1) {
926 		return false;
927 	}
928 	if (_vlen) {
929 		*_vlen = vlen;
930 	}
931 	if (key) {
932 		key[0] = 0;
933 		if (klen > SDB_MIN_KEY && klen < SDB_MAX_KEY) {
934 			if (getbytes (s, key, klen) == -1) {
935 				return 0;
936 			}
937 			key[klen] = 0;
938 		}
939 	}
940 	if (value) {
941 		*value = 0;
942 		if (vlen < SDB_MAX_VALUE) {
943 			*value = malloc (vlen + 10);
944 			if (!*value) {
945 				return false;
946 			}
947 			if (getbytes (s, *value, vlen) == -1) {
948 				free (*value);
949 				*value = NULL;
950 				return false;
951 			}
952 			(*value)[vlen] = 0;
953 		}
954 	}
955 	return true;
956 }
957 
parse_expire(ut64 e)958 static inline ut64 parse_expire (ut64 e) {
959 	const ut64 month = 30 * 24 * 60 * 60;
960 	if (e > 0 && e < month) {
961 		e += sdb_now ();
962 	}
963 	return e;
964 }
965 
sdb_expire_set(Sdb * s,const char * key,ut64 expire,ut32 cas)966 SDB_API bool sdb_expire_set(Sdb* s, const char *key, ut64 expire, ut32 cas) {
967 	char *buf;
968 	ut32 pos, len;
969 	SdbKv *kv;
970 	bool found;
971 	s->timestamped = true;
972 	if (!key) {
973 		s->expire = parse_expire (expire);
974 		return true;
975 	}
976 	kv = (SdbKv*)sdb_ht_find_kvp (s->ht, key, &found);
977 	if (found && kv) {
978 		if (*sdbkv_value (kv)) {
979 			if (!cas || cas == kv->cas) {
980 				kv->expire = parse_expire (expire);
981 				return true;
982 			}
983 		}
984 		return false;
985 	}
986 	if (s->fd == -1) {
987 		return false;
988 	}
989 	(void) cdb_findstart (&s->db);
990 	if (!cdb_findnext (&s->db, sdb_hash (key), key, strlen (key) + 1)) {
991 		return false;
992 	}
993 	pos = cdb_datapos (&s->db);
994 	len = cdb_datalen (&s->db);
995 	if (len < 1 || len >= INT32_MAX) {
996 		return false;
997 	}
998 	if (!(buf = calloc (1, len + 1))) {
999 		return false;
1000 	}
1001 	cdb_read (&s->db, buf, len, pos);
1002 	buf[len] = 0;
1003 	sdb_set_owned (s, key, buf, cas);
1004 	return sdb_expire_set (s, key, expire, cas); // recursive
1005 }
1006 
sdb_expire_get(Sdb * s,const char * key,ut32 * cas)1007 SDB_API ut64 sdb_expire_get(Sdb* s, const char *key, ut32 *cas) {
1008 	bool found = false;
1009 	SdbKv *kv = (SdbKv*)sdb_ht_find_kvp (s->ht, key, &found);
1010 	if (found && kv && *sdbkv_value (kv)) {
1011 		if (cas) {
1012 			*cas = kv->cas;
1013 		}
1014 		return kv->expire;
1015 	}
1016 	return 0LL;
1017 }
1018 
sdb_hook(Sdb * s,SdbHook cb,void * user)1019 SDB_API bool sdb_hook(Sdb* s, SdbHook cb, void* user) {
1020 	int i = 0;
1021 	SdbHook hook;
1022 	SdbListIter *iter;
1023 	if (s->hooks) {
1024 		ls_foreach (s->hooks, iter, hook) {
1025 			if (!(i % 2) && (hook == cb)) {
1026 				return false;
1027 			}
1028 			i++;
1029 		}
1030 	} else {
1031 		s->hooks = ls_new ();
1032 		s->hooks->free = NULL;
1033 	}
1034 	ls_append (s->hooks, (void*)cb);
1035 	ls_append (s->hooks, user);
1036 	return true;
1037 }
1038 
sdb_unhook(Sdb * s,SdbHook h)1039 SDB_API bool sdb_unhook(Sdb* s, SdbHook h) {
1040 	int i = 0;
1041 	SdbHook hook;
1042 	SdbListIter *iter, *iter2;
1043 	ls_foreach (s->hooks, iter, hook) {
1044 		if (!(i % 2) && (hook == h)) {
1045 			iter2 = iter->n;
1046 			ls_delete (s->hooks, iter);
1047 			ls_delete (s->hooks, iter2);
1048 			return true;
1049 		}
1050 		i++;
1051 	}
1052 	return false;
1053 }
1054 
sdb_hook_call(Sdb * s,const char * k,const char * v)1055 SDB_API int sdb_hook_call(Sdb *s, const char *k, const char *v) {
1056 	SdbListIter *iter;
1057 	SdbHook hook;
1058 	int i = 0;
1059 	if (s->timestamped && s->last) {
1060 		s->last = sdb_now ();
1061 	}
1062 	ls_foreach (s->hooks, iter, hook) {
1063 		if (!(i % 2) && k && iter->n) {
1064 			void *u = iter->n->data;
1065 			hook (s, u, k, v);
1066 		}
1067 		i++;
1068 	}
1069 	return i >> 1;
1070 }
1071 
sdb_hook_free(Sdb * s)1072 SDB_API void sdb_hook_free(Sdb *s) {
1073 	ls_free (s->hooks);
1074 	s->hooks = NULL;
1075 }
1076 
sdb_config(Sdb * s,int options)1077 SDB_API void sdb_config(Sdb *s, int options) {
1078 	s->options = options;
1079 	if (options & SDB_OPTION_SYNC) {
1080 		// sync on every query
1081 	}
1082 	if (options & SDB_OPTION_JOURNAL) {
1083 		// sync on every query
1084 		sdb_journal_open (s);
1085 		// load journaling if exists
1086 		sdb_journal_load (s);
1087 		sdb_journal_clear (s);
1088 	} else {
1089 		sdb_journal_close (s);
1090 	}
1091 	if (options & SDB_OPTION_NOSTAMP) {
1092 		// sync on every query
1093 		s->last = 0LL;
1094 	}
1095 	if (options & SDB_OPTION_FS) {
1096 		// have access to fs (handle '.' or not in query)
1097 	}
1098 }
1099 
sdb_unlink(Sdb * s)1100 SDB_API bool sdb_unlink(Sdb* s) {
1101 	sdb_fini (s, true);
1102 	return sdb_disk_unlink (s);
1103 }
1104 
sdb_drain(Sdb * s,Sdb * f)1105 SDB_API void sdb_drain(Sdb *s, Sdb *f) {
1106 	if (s && f) {
1107 		f->refs = s->refs;
1108 		sdb_fini (s, true);
1109 		*s = *f;
1110 		free (f);
1111 	}
1112 }
1113 
copy_foreach_cb(void * user,const char * k,const char * v)1114 static bool copy_foreach_cb(void *user, const char *k, const char *v) {
1115 	Sdb *dst = user;
1116 	sdb_set (dst, k, v, 0);
1117 	return true;
1118 }
1119 
sdb_copy(Sdb * src,Sdb * dst)1120 SDB_API void sdb_copy(Sdb *src, Sdb *dst) {
1121 	sdb_foreach (src, copy_foreach_cb, dst);
1122 	SdbListIter *it;
1123 	SdbNs *ns;
1124 	ls_foreach (src->ns, it, ns) {
1125 		sdb_copy (ns->sdb, sdb_ns (dst, ns->name, true));
1126 	}
1127 }
1128 
1129 typedef struct {
1130 	Sdb *sdb;
1131 	const char *key;
1132 } UnsetCallbackData;
1133 
unset_cb(void * user,const char * k,const char * v)1134 static bool unset_cb(void *user, const char *k, const char *v) {
1135 	UnsetCallbackData *ucd = user;
1136 	if (sdb_match (k, ucd->key)) {
1137 		sdb_unset (ucd->sdb, k, 0);
1138 	}
1139 	return true;
1140 }
1141 
sdb_unset_like(Sdb * s,const char * k)1142 SDB_API int sdb_unset_like(Sdb *s, const char *k) {
1143 	UnsetCallbackData ucd = { s, k };
1144 	return sdb_foreach (s, unset_cb, &ucd);
1145 }
1146 
1147 typedef struct {
1148 	Sdb *sdb;
1149 	const char *key;
1150 	const char *val;
1151 	SdbForeachCallback cb;
1152 	const char **array;
1153 	int array_index;
1154 	int array_size;
1155 } LikeCallbackData;
1156 
like_cb(void * user,const char * k,const char * v)1157 static bool like_cb(void *user, const char *k, const char *v) {
1158 	LikeCallbackData *lcd = user;
1159 	if (!user) {
1160 		return false;
1161 	}
1162 	if (k && lcd->key && !sdb_match (k, lcd->key)) {
1163 		return true;
1164 	}
1165 	if (v && lcd->val && !sdb_match (v, lcd->val)) {
1166 		return true;
1167 	}
1168 	if (lcd->array) {
1169 		int idx = lcd->array_index;
1170 		int newsize = lcd->array_size + sizeof (char*) * 2;
1171 		const char **newarray = (const char **)realloc ((void*)lcd->array, newsize);
1172 		if (!newarray) {
1173 			return false;
1174 		}
1175 		lcd->array = newarray;
1176 		lcd->array_size = newsize;
1177 		// concatenate in array
1178 		lcd->array[idx] = k;
1179 		lcd->array[idx + 1] = v;
1180 		lcd->array[idx + 2] = NULL;
1181 		lcd->array[idx + 3] = NULL;
1182 		lcd->array_index = idx+2;
1183 	} else {
1184 		if (lcd->cb) {
1185 			lcd->cb (lcd->sdb, k, v);
1186 		}
1187 	}
1188 	return true;
1189 }
1190 
sdb_like(Sdb * s,const char * k,const char * v,SdbForeachCallback cb)1191 SDB_API char** sdb_like(Sdb *s, const char *k, const char *v, SdbForeachCallback cb) {
1192 	LikeCallbackData lcd = { s, k, v, cb, NULL, 0, 0 };
1193 	if (cb) {
1194 		sdb_foreach (s, like_cb, &lcd);
1195 		return NULL;
1196 	}
1197 	if (k && !*k) {
1198 		lcd.key = NULL;
1199 	}
1200 	if (v && !*v) {
1201 		lcd.val = NULL;
1202 	}
1203 	lcd.array_size = sizeof (char*) * 2;
1204 	lcd.array = calloc (lcd.array_size, 1);
1205 	if (!lcd.array) {
1206 		return NULL;
1207 	}
1208 	lcd.array_index = 0;
1209 	sdb_foreach (s, like_cb, &lcd);
1210 	if (lcd.array_index == 0) {
1211 		free ((void*)lcd.array);
1212 		return NULL;
1213 	}
1214 	return (char**)lcd.array;
1215 }
1216