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