From faac2436b1d755eb78d1f6d7f9c9b4c10fbd9b63 Mon Sep 17 00:00:00 2001 From: Riccardo Schirone Date: Fri, 1 Jul 2022 22:40:31 +0200 Subject: [PATCH] Move SDB into RzUtil * Make SDB code SPDX compliant * Use some RzUtil functions inside SDB util: compile natively without any extra dependency --- .reuse/dep5 | 8 + LICENSES/CC-PDDC.txt | 8 + librz/util/meson.build | 87 ++- librz/util/sdb/README.md | 88 +++ librz/util/sdb/src/array.c | 715 +++++++++++++++++ librz/util/sdb/src/base64.c | 69 ++ librz/util/sdb/src/buffer.c | 61 ++ librz/util/sdb/src/buffer.h | 67 ++ librz/util/sdb/src/cdb.c | 194 +++++ librz/util/sdb/src/cdb.h | 48 ++ librz/util/sdb/src/cdb_make.c | 196 +++++ librz/util/sdb/src/cdb_make.h | 52 ++ librz/util/sdb/src/diff.c | 173 +++++ librz/util/sdb/src/disk.c | 123 +++ librz/util/sdb/src/fmt.c | 232 ++++++ librz/util/sdb/src/ht_inc.c | 369 +++++++++ librz/util/sdb/src/ht_inc.h | 131 ++++ librz/util/sdb/src/ht_pp.c | 52 ++ librz/util/sdb/src/ht_pp.h | 29 + librz/util/sdb/src/ht_pu.c | 28 + librz/util/sdb/src/ht_pu.h | 27 + librz/util/sdb/src/ht_up.c | 46 ++ librz/util/sdb/src/ht_up.h | 29 + librz/util/sdb/src/ht_uu.c | 22 + librz/util/sdb/src/ht_uu.h | 27 + librz/util/sdb/src/journal.c | 110 +++ librz/util/sdb/src/json.c | 356 +++++++++ librz/util/sdb/src/json/README | 20 + librz/util/sdb/src/json/api.c | 55 ++ librz/util/sdb/src/json/indent.c | 140 ++++ librz/util/sdb/src/json/js0n.c | 306 ++++++++ librz/util/sdb/src/json/main.c | 97 +++ librz/util/sdb/src/json/path.c | 176 +++++ librz/util/sdb/src/json/rangstr.c | 121 +++ librz/util/sdb/src/json/rangstr.h | 32 + librz/util/sdb/src/json/test.c | 14 + librz/util/sdb/src/json/test.json | 4 + librz/util/sdb/src/lock.c | 81 ++ librz/util/sdb/src/ls.c | 378 +++++++++ librz/util/sdb/src/ls.h | 88 +++ librz/util/sdb/src/main.c | 532 +++++++++++++ librz/util/sdb/src/match.c | 120 +++ librz/util/sdb/src/meson.build | 51 ++ librz/util/sdb/src/ns.c | 253 +++++++ librz/util/sdb/src/num.c | 89 +++ librz/util/sdb/src/query.c | 921 ++++++++++++++++++++++ librz/util/sdb/src/sdb.c | 1179 +++++++++++++++++++++++++++++ librz/util/sdb/src/sdb.h | 401 ++++++++++ librz/util/sdb/src/sdb_private.h | 59 ++ librz/util/sdb/src/sdbht.c | 69 ++ librz/util/sdb/src/sdbht.h | 63 ++ librz/util/sdb/src/set.c | 48 ++ librz/util/sdb/src/set.h | 34 + librz/util/sdb/src/text.c | 439 +++++++++++ librz/util/sdb/src/util.c | 283 +++++++ meson.build | 317 ++++---- test/unit/meson.build | 6 + test/unit/test_sdb_array.c | 65 ++ test/unit/test_sdb_diff.c | 258 +++++++ test/unit/test_sdb_hash.c | 564 ++++++++++++++ test/unit/test_sdb_ls.c | 454 +++++++++++ test/unit/test_sdb_sdb.c | 579 ++++++++++++++ test/unit/test_sdb_util.c | 36 + 63 files changed, 11520 insertions(+), 159 deletions(-) create mode 100644 LICENSES/CC-PDDC.txt create mode 100644 librz/util/sdb/README.md create mode 100644 librz/util/sdb/src/array.c create mode 100644 librz/util/sdb/src/base64.c create mode 100644 librz/util/sdb/src/buffer.c create mode 100644 librz/util/sdb/src/buffer.h create mode 100644 librz/util/sdb/src/cdb.c create mode 100644 librz/util/sdb/src/cdb.h create mode 100644 librz/util/sdb/src/cdb_make.c create mode 100644 librz/util/sdb/src/cdb_make.h create mode 100644 librz/util/sdb/src/diff.c create mode 100644 librz/util/sdb/src/disk.c create mode 100644 librz/util/sdb/src/fmt.c create mode 100644 librz/util/sdb/src/ht_inc.c create mode 100644 librz/util/sdb/src/ht_inc.h create mode 100644 librz/util/sdb/src/ht_pp.c create mode 100644 librz/util/sdb/src/ht_pp.h create mode 100644 librz/util/sdb/src/ht_pu.c create mode 100644 librz/util/sdb/src/ht_pu.h create mode 100644 librz/util/sdb/src/ht_up.c create mode 100644 librz/util/sdb/src/ht_up.h create mode 100644 librz/util/sdb/src/ht_uu.c create mode 100644 librz/util/sdb/src/ht_uu.h create mode 100644 librz/util/sdb/src/journal.c create mode 100644 librz/util/sdb/src/json.c create mode 100644 librz/util/sdb/src/json/README create mode 100644 librz/util/sdb/src/json/api.c create mode 100644 librz/util/sdb/src/json/indent.c create mode 100644 librz/util/sdb/src/json/js0n.c create mode 100644 librz/util/sdb/src/json/main.c create mode 100644 librz/util/sdb/src/json/path.c create mode 100644 librz/util/sdb/src/json/rangstr.c create mode 100644 librz/util/sdb/src/json/rangstr.h create mode 100644 librz/util/sdb/src/json/test.c create mode 100644 librz/util/sdb/src/json/test.json create mode 100644 librz/util/sdb/src/lock.c create mode 100644 librz/util/sdb/src/ls.c create mode 100644 librz/util/sdb/src/ls.h create mode 100644 librz/util/sdb/src/main.c create mode 100644 librz/util/sdb/src/match.c create mode 100644 librz/util/sdb/src/meson.build create mode 100644 librz/util/sdb/src/ns.c create mode 100644 librz/util/sdb/src/num.c create mode 100644 librz/util/sdb/src/query.c create mode 100644 librz/util/sdb/src/sdb.c create mode 100644 librz/util/sdb/src/sdb.h create mode 100644 librz/util/sdb/src/sdb_private.h create mode 100644 librz/util/sdb/src/sdbht.c create mode 100644 librz/util/sdb/src/sdbht.h create mode 100644 librz/util/sdb/src/set.c create mode 100644 librz/util/sdb/src/set.h create mode 100644 librz/util/sdb/src/text.c create mode 100644 librz/util/sdb/src/util.c create mode 100644 test/unit/test_sdb_array.c create mode 100644 test/unit/test_sdb_diff.c create mode 100644 test/unit/test_sdb_hash.c create mode 100644 test/unit/test_sdb_ls.c create mode 100644 test/unit/test_sdb_sdb.c create mode 100644 test/unit/test_sdb_util.c diff --git a/.reuse/dep5 b/.reuse/dep5 index 248d72b2f75..16d3e8faa29 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -255,3 +255,11 @@ License: LGPL-3.0-only Files: librz/reg/p/x86-linux.regs Copyright: 2009 pancake License: LGPL-3.0-only + +Files: librz/util/sdb/src/json/README +Copyright: pancake +License: MIT + +Files: librz/util/sdb/src/json/test.json +Copyright: pancake +License: MIT diff --git a/LICENSES/CC-PDDC.txt b/LICENSES/CC-PDDC.txt new file mode 100644 index 00000000000..b64dfd6b700 --- /dev/null +++ b/LICENSES/CC-PDDC.txt @@ -0,0 +1,8 @@ + +The person or persons who have associated work with this document (the "Dedicator" or "Certifier") hereby either (a) certifies that, to the best of his knowledge, the work of authorship identified is in the public domain of the country from which the work is published, or (b) hereby dedicates whatever copyright the dedicators holds in the work of authorship identified below (the "Work") to the public domain. A certifier, moreover, dedicates any copyright interest he may have in the associated work, and for these purposes, is described as a "dedicator" below. + +A certifier has taken reasonable steps to verify the copyright status of this work. Certifier recognizes that his good faith efforts may not shield him from liability if in fact the work certified is not in the public domain. + +Dedicator makes this dedication for the benefit of the public at large and to the detriment of the Dedicator's heirs and successors. Dedicator intends this dedication to be an overt act of relinquishment in perpetuity of all present and future rights under copyright law, whether vested or contingent, in the Work. Dedicator understands that such relinquishment of all rights includes the relinquishment of all rights to enforce (by lawsuit or otherwise) those copyrights in the Work. + +Dedicator recognizes that, once placed in the public domain, the Work may be freely reproduced, distributed, transmitted, used, modified, built upon, or otherwise exploited by anyone for any purpose, commercial or non-commercial, and in any way, including by methods that have not yet been invented or conceived. diff --git a/librz/util/meson.build b/librz/util/meson.build index 8714d8bfca6..20480ef813f 100644 --- a/librz/util/meson.build +++ b/librz/util/meson.build @@ -1,3 +1,4 @@ +subdir('sdb/src') rz_util_sources = [ 'alloc.c', @@ -81,7 +82,7 @@ rz_util_sources = [ 'x509.c', ] -rz_util_deps = [ldl, lrt, mth, th, utl, sdb_dep] + platform_deps +rz_util_deps = [ldl, lrt, mth, th, utl] + platform_deps if zlib_dep.found() rz_util_deps += [zlib_dep] endif @@ -92,6 +93,13 @@ if ['freebsd', 'netbsd', 'haiku', 'dragonfly'].contains(host_machine.system()) # backtrace_symbols_fd requires -lexecinfo rz_util_deps += [cc.find_library('execinfo', static: is_static_build)] endif +if meson.is_cross_build() + execinfo_native = disabler() + if ['freebsd', 'netbsd', 'haiku', 'dragonfly'].contains(build_machine.system()) + # backtrace_symbols_fd requires -lexecinfo + execinfo_native = cc_native.find_library('execinfo', static: is_static_build) + endif +endif if sys_openssl.found() rz_util_deps += [sys_openssl] @@ -100,8 +108,10 @@ else rz_util_sources += ['big.c'] endif -rz_util = library('rz_util', rz_util_sources, - include_directories: platform_inc, +rz_util_includes = [platform_inc, include_directories('sdb/src', 'sdb')] + +rz_util = library('rz_util', rz_util_sources, libsdb_sources, + include_directories: rz_util_includes, dependencies: rz_util_deps, install: true, implicit_include_directories: false, @@ -114,13 +124,61 @@ rz_util = library('rz_util', rz_util_sources, rz_util_dep = declare_dependency( link_with: rz_util, - include_directories: platform_inc, - dependencies: [ - sdb_dep.partial_dependency(includes: true), - ] + include_directories: rz_util_includes, ) meson.override_dependency('rz_util', rz_util_dep) +if meson.is_cross_build() + # this is a bit messy, it duplicates most of the code required to get the + # regular dependencies but for the build_system. This is required to build + # sdb_native, used at build time to compile .sdb.txt files into .sdb ones. + platform_native_deps = [] + if build_machine.system() == 'windows' + platform_native_deps = [ + cc_native.find_library('ws2_32'), + cc_native.find_library('wininet'), + cc_native.find_library('psapi'), + ] + endif + rz_util_native_deps = [ldl_native, lrt_native, mth_native, th_native, utl_native] + platform_native_deps + if execinfo_native.found() + rz_util_native_deps += [execinfo_native] + endif + # do not use external libs, we do not need them + userconf_native.set10('HAVE_LIB_MAGIC', false) + userconf_native.set10('USE_LIB_MAGIC', false) + userconf_native.set10('HAVE_LIB_XXHASH', false) + userconf_native.set10('USE_LIB_XXHASH', false) + userconf_native.set10('HAVE_OPENSSL', false) + userconf_native.set10('HAVE_LIBUV', false) + userconf_native.set10('HAVE_LZMA', false) + userconf_native.set10('HAVE_ZLIB', false) + + rz_userconf_native_h = configure_file( + input: rz_userconf_h_in, + output: 'rz_userconf.h', + configuration: userconf_native, + install_dir: rizin_incdir + ) + rz_util_native_includes = [include_directories('.'), rz_util_includes] + + rz_util_native = static_library('rz_util_native', rz_util_sources, libsdb_sources, + include_directories: rz_util_native_includes, + dependencies: rz_util_native_deps, + implicit_include_directories: false, + install_rpath: rpath_lib, + install: false, + native: true, + ) + + rz_util_native_dep = declare_dependency( + link_with: rz_util_native, + include_directories: rz_util_native_includes, + ) +else + rz_util_native_dep = rz_util_dep +endif + pkgconfig_mod.generate(rz_util, subdirs: ['librz', 'librz/sdb'], version: rizin_version, @@ -149,3 +207,18 @@ if not is_static_libs_only configuration: conf, ) endif + +sdb_exe = executable('sdb_native', 'sdb/src/main.c', + dependencies: rz_util_native_dep, + install: false, + native: true, + implicit_include_directories: false, + install_rpath: rpath_exe, +) + +sdb_gen_cmd = [ + sdb_exe, + '@OUTPUT@', + '==', + '@INPUT@' +] diff --git a/librz/util/sdb/README.md b/librz/util/sdb/README.md new file mode 100644 index 00000000000..b2314621605 --- /dev/null +++ b/librz/util/sdb/README.md @@ -0,0 +1,88 @@ +SDB (string database) +===================== + +sdb is a simple string key/value database based on djb's cdb +disk storage and supports JSON and arrays introspection. + +Author +------ +pancake + +Contains +-------- +* namespaces (multiple sdb paths) +* atomic database sync (never corrupted) +* commandline frontend for sdb databases +* arrays support (syntax sugar) +* json parser/getter (js0n.c) + +Rips +---- +* disk storage based on cdb code +* linked lists from rizin api + +Compilation +----------- +SDB requires [Meson](https://mesonbuild.com/) and [Ninja](https://ninja-build.org/) buildsystems to be built: +``` +meson build +ninja -C build +``` + +Changes +------- +I have modified cdb code a little to create smaller databases and +be memory leak free in order to use it from a library. + +The sdb's cdb database format is 10% smaller than the original +one. This is because keylen and valuelen are encoded in 4 bytes: +1 for the key length and 3 for the value length. + +In a test case, a 4.3MB cdb database takes only 3.9MB after this +file format change. + +Usage example +------------- +Let's create a database! +``` +$ sdb d hello=world +$ sdb d hello +world +``` +Using arrays (>=0.6): +``` +$ sdb - '[]list=1,2' '[0]list' '[0]list=foo' '[]list' '[+1]list=bar' +1 +foo +2 +``` +Let's play with json: +``` +$ sdb d g='{"foo":1,"bar":{"cow":3}}' +$ sdb d g:bar.cow +3 +$ sdb - user='{"id":123}' user:id=99 user:id +99 +``` +Using the commandline without any disk database: +``` +$ sdb - foo=bar foo a=3 +a -a +bar +4 +3 +``` +``` +$ sdb - +foo=bar +foo +bar +a=3 ++a +4 +-a +3 +``` +Remove the database +``` +$ rm -f d +``` diff --git a/librz/util/sdb/src/array.c b/librz/util/sdb/src/array.c new file mode 100644 index 00000000000..07922f57645 --- /dev/null +++ b/librz/util/sdb/src/array.c @@ -0,0 +1,715 @@ +// SPDX-FileCopyrightText: 2011-2018 pancake +// SPDX-License-Identifier: MIT + +#include "sdb.h" +#include + +// TODO: Push should always prepend. do not make this configurable +#define PUSH_PREPENDS 1 + +// TODO: missing num_{inc/dec} functions + +static const char *Aindexof(const char *str, int idx) { + int len = 0; + const char *n, *p = str; + for (len = 0;; len++) { + if (len == idx) { + return p; + } + if (!(n = strchr(p, SDB_RS))) { + break; + } + p = n + 1; + } + return NULL; +} + +static int astrcmp(const char *a, const char *b) { + register char va = *a; + register char vb = *b; + for (;;) { + if (va == '\0' || va == SDB_RS) { + if (vb == '\0' || vb == SDB_RS) { + return 0; + } + return -1; + } + if (vb == '\0' || vb == SDB_RS) { + return 1; + } + if (va != vb) { + return (va > vb) ? 1 : -1; + } + va = *(++a); + vb = *(++b); + } +} + +static inline int cstring_cmp(const void *a, const void *b) { + const char **va = (const char **)a; + const char **vb = (const char **)b; + return astrcmp(*va, *vb); +} + +static inline int int_cmp(const void *a, const void *b) { + const ut64 va = *(const ut64 *)a; + const ut64 vb = *(const ut64 *)b; + if (va > vb) { + return 1; + } + if (va < vb) { + return -1; + } + return 0; +} + +RZ_API ut64 sdb_array_get_num(Sdb *s, const char *key, int idx, ut32 *cas) { + int i; + const char *n, *str = sdb_const_get(s, key, cas); + if (!str || !*str) { + return 0LL; + } + if (idx) { + for (i = 0; i < idx; i++) { + n = strchr(str, SDB_RS); + if (!n) { + return 0LL; + } + str = n + 1; + } + } + return sdb_atoi(str); +} + +RZ_API char *sdb_array_get(Sdb *s, const char *key, int idx, ut32 *cas) { + const char *str = sdb_const_get(s, key, cas); + const char *p = str; + char *o, *n; + int i, len; + if (!str || !*str) { + return NULL; + } + if (idx < 0) { + int len = sdb_alen(str); + idx = -idx; + if (idx > len) { + return NULL; + } + idx = len - idx; + } + if (!idx) { + n = strchr(str, SDB_RS); + if (!n) { + return strdup(str); + } + len = n - str; + o = malloc(len + 1); + if (!o) { + return NULL; + } + memcpy(o, str, len); + o[len] = 0; + return o; + } + for (i = 0; i < idx; i++) { + n = strchr(p, SDB_RS); + if (!n) + return NULL; + p = n + 1; + } + n = strchr(p, SDB_RS); + if (!n) { + return strdup(p); + } + len = n - p; + o = malloc(len + 1); + if (o) { + memcpy(o, p, len); + o[len] = 0; + return o; + } + return NULL; +} + +RZ_API int sdb_array_insert_num(Sdb *s, const char *key, int idx, ut64 val, + ut32 cas) { + char valstr[64]; + return sdb_array_insert(s, key, idx, + sdb_itoa(val, valstr, SDB_NUM_BASE), cas); +} + +// TODO: done, but there's room for improvement +RZ_API int sdb_array_insert(Sdb *s, const char *key, int idx, const char *val, + ut32 cas) { + int lnstr, lstr; + size_t lval; + char *x, *ptr; + const char *str = sdb_const_get_len(s, key, &lstr, 0); + if (!str || !*str) { + return sdb_set(s, key, val, cas); + } + lval = strlen(val); + lstr--; + // XXX: lstr is wrongly computed in sdb_const_get_with an off-by-one + // we can optimize this by caching value len in memory . add + // sdb_const_get_size() + lstr = strlen(str); + + // When removing strlen this conversion should be checked + size_t lstr_tmp = lstr; + if (SZT_ADD_OVFCHK(lval, lstr_tmp) || SZT_ADD_OVFCHK(lval + lstr_tmp, 2)) { + return false; + } + x = malloc(lval + lstr_tmp + 2); + if (!x) { + return false; + } + + if (idx == -1) { + memcpy(x, str, lstr); + x[lstr] = SDB_RS; + memcpy(x + lstr + 1, val, lval + 1); + } else if (!idx) { + memcpy(x, val, lval); + x[lval] = SDB_RS; + memcpy(x + lval + 1, str, lstr + 1); + } else { + char *nstr = malloc(lstr + 1); + if (!nstr) { + free(x); + return false; + } + memcpy(nstr, str, lstr + 1); + ptr = (char *)Aindexof(nstr, idx); + if (ptr) { + int lptr = (nstr + lstr + 1) - ptr; + char *p_1 = ptr > nstr ? ptr - 1 : ptr; + *p_1 = 0; + lnstr = ptr - nstr - 1; + memcpy(x, nstr, lnstr); + x[lnstr] = SDB_RS; + memcpy(x + lnstr + 1, val, lval); + x[lnstr + lval + 1] = SDB_RS; + // TODO: this strlen hurts performance + memcpy(x + lval + 2 + lnstr, ptr, lptr); // strlen (ptr)+1); + free(nstr); + } else { + // this is not efficient + free(nstr); + free(x); + // fallback for empty buckets + return sdb_array_set(s, key, idx, val, cas); + } + } + return sdb_set_owned(s, key, x, cas); +} + +RZ_API int sdb_array_set_num(Sdb *s, const char *key, int idx, ut64 val, + ut32 cas) { + char valstr[SDB_NUM_BUFSZ]; + return sdb_array_set(s, key, idx, sdb_itoa(val, valstr, SDB_NUM_BASE), + cas); +} + +RZ_API int sdb_array_add_num(Sdb *s, const char *key, ut64 val, ut32 cas) { + char buf[SDB_NUM_BUFSZ]; + char *v = sdb_itoa(val, buf, SDB_NUM_BASE); + if (!sdb_array_contains(s, key, v, NULL)) { + if (val < 256) { + char *v = sdb_itoa(val, buf, 10); + return sdb_array_add(s, key, v, cas); + } + } + return sdb_array_add(s, key, v, cas); +} + +// XXX: index should be supressed here? if its a set we shouldnt change the index +RZ_API int sdb_array_add(Sdb *s, const char *key, const char *val, ut32 cas) { + if (sdb_array_contains(s, key, val, NULL)) { + return 0; + } + return sdb_array_insert(s, key, -1, val, cas); +} + +RZ_API int sdb_array_add_sorted(Sdb *s, const char *key, const char *val, ut32 cas) { + int lstr, lval, i, j; + const char *str_e, *str_lp, *str_p, *str = sdb_const_get_len(s, key, &lstr, 0); + char *nstr, *nstr_p, **vals; + const char null = '\0'; + if (!str || !*str) { + str = &null; + lstr = 0; + } + str_e = str + lstr; + str_lp = str_p = str; + if (!val || !*val) { + return 1; + } + lval = strlen(val); + vals = sdb_fmt_array(val); + for (i = 0; vals[i]; i++) { + /* empty */ + } + if (i > 1) { + qsort(vals, i, sizeof(ut64 *), cstring_cmp); + } + nstr_p = nstr = malloc(lstr + lval + 3); + if (!nstr) { + return 1; + } + for (i = 0; vals[i]; i++) { + while (str_p < str_e) { + if (astrcmp(vals[i], str_p) < 0) { + break; + } + str_p = sdb_const_anext(str_p); + if (!str_p) { + str_p = str_e; + } + } + memcpy(nstr_p, str_lp, str_p - str_lp); + nstr_p += str_p - str_lp; + if (str_p == str_e && str_lp != str_e) { + *(nstr_p++) = SDB_RS; + } + str_lp = str_p; + j = strlen(vals[i]); + memcpy(nstr_p, vals[i], j); + nstr_p += j; + *(nstr_p++) = SDB_RS; + } + if (str_lp < str_e) { + memcpy(nstr_p, str_lp, str_e - str_lp); + nstr_p += str_e - str_lp; + *(nstr_p) = '\0'; + } else { + *(--nstr_p) = '\0'; + } + sdb_set_owned(s, key, nstr, cas); + free(vals); + return 0; +} + +RZ_API int sdb_array_add_sorted_num(Sdb *s, const char *key, ut64 val, + ut32 cas) { + int i; + char valstr[SDB_NUM_BUFSZ]; + const char *str = sdb_const_get(s, key, 0); + const char *n = str; + if (!str || !*str) { + return sdb_set(s, key, sdb_itoa(val, valstr, SDB_NUM_BASE), cas); + } + for (i = 0; n; i++) { + if (val <= sdb_atoi(n)) { + break; + } + n = sdb_const_anext(n); + } + return sdb_array_insert_num(s, key, n ? i : -1, val, cas); +} + +RZ_API int sdb_array_unset(Sdb *s, const char *key, int idx, ut32 cas) { + return sdb_array_set(s, key, idx, "", cas); +} + +RZ_API bool sdb_array_append(Sdb *s, const char *key, const char *val, + ut32 cas) { +#if SLOW + return sdb_array_set(s, key, -1, val, cas); +#else + int str_len = 0; + ut32 kas = cas; + const char *str = sdb_const_get_len(s, key, &str_len, &kas); + if (!val || (cas && cas != kas)) { + return false; + } + cas = kas; + if (str && *str && str_len > 0) { + int val_len = strlen(val); + char *newval = malloc(str_len + val_len + 2); + if (!newval) { + return false; + } + memcpy(newval, str, str_len); + newval[str_len] = SDB_RS; + memcpy(newval + str_len + 1, val, val_len); + newval[str_len + val_len + 1] = 0; + sdb_set_owned(s, key, newval, cas); + } else { + sdb_set(s, key, val, cas); + } + return true; +#endif +} + +RZ_API bool sdb_array_append_num(Sdb *s, const char *key, ut64 val, ut32 cas) { + return sdb_array_set_num(s, key, -1, val, cas); +} + +RZ_API int sdb_array_set(Sdb *s, const char *key, int idx, const char *val, + ut32 cas) { + int lstr, lval, len; + const char *usr, *str = sdb_const_get_len(s, key, &lstr, 0); + char *ptr; + + if (!str || !*str) { + return sdb_set(s, key, val, cas); + } + // XXX: should we cache sdb_alen value inside kv? + len = sdb_alen(str); + lstr--; + if (idx < 0 || idx == len) { // append + return sdb_array_insert(s, key, -1, val, cas); + } + lval = strlen(val); + if (idx > len) { + int ret, i, ilen = idx - len; + char *newkey = malloc(ilen + lval + 1); + if (!newkey) { + return 0; + } + for (i = 0; i < ilen; i++) { + newkey[i] = SDB_RS; + } + memcpy(newkey + i, val, lval + 1); + ret = sdb_array_insert(s, key, -1, newkey, cas); + free(newkey); + return ret; + } + // lstr = strlen (str); + ptr = (char *)Aindexof(str, idx); + if (ptr) { + int diff = ptr - str; + char *nstr = malloc(lstr + lval + 2); + if (!nstr) { + return false; + } + ptr = nstr + diff; + // memcpy (nstr, str, lstr+1); + memcpy(nstr, str, diff); + memcpy(ptr, val, lval + 1); + usr = Aindexof(str, idx + 1); + if (usr) { + ptr[lval] = SDB_RS; + strcpy(ptr + lval + 1, usr); + } + return sdb_set_owned(s, key, nstr, 0); + } + return 0; +} + +RZ_API int sdb_array_remove_num(Sdb *s, const char *key, ut64 val, ut32 cas) { + const char *n, *p, *str = sdb_const_get(s, key, 0); + int idx = 0; + ut64 num; + if (str) { + for (p = str;; idx++) { + num = sdb_atoi(p); + if (num == val) { + return sdb_array_delete(s, key, idx, cas); + } + n = strchr(p, SDB_RS); + if (!n) { + break; + } + p = n + 1; + } + } + return 0; +} + +/* get array index of given value */ +RZ_API int sdb_array_indexof(Sdb *s, const char *key, const char *val, + ut32 cas) { + const char *str = sdb_const_get(s, key, 0); + const char *n, *p = str; + int i; + for (i = 0;; i++) { + if (!p) { + break; + } + if (!astrcmp(p, val)) { + return i; + } + n = strchr(p, SDB_RS); + if (!n) + break; + p = n + 1; + } + return -1; +} + +// previously named del_str... pair with _add +RZ_API int sdb_array_remove(Sdb *s, const char *key, const char *val, + ut32 cas) { + const char *str = sdb_const_get(s, key, 0); + const char *n, *p = str; + int idx; + if (p) { + for (idx = 0;; idx++) { + if (!astrcmp(p, val)) { + return sdb_array_delete(s, key, idx, cas); + } + n = strchr(p, SDB_RS); + if (!n) { + break; + } + p = n + 1; + } + } + return 0; +} + +RZ_API int sdb_array_delete(Sdb *s, const char *key, int idx, ut32 cas) { + int i; + char *p, *n, *str = sdb_get(s, key, 0); + p = str; + if (!str || !*str) { + free(str); + return 0; + } + if (idx < 0) { + idx = sdb_alen(str); + if (idx) + idx--; + } + for (i = 0; i < idx; i++) { + if ((n = strchr(p, SDB_RS))) { + p = n + 1; + } else { + free(str); + return 0; + } + } + n = strchr(p, SDB_RS); + if (n) { + memmove(p, n + 1, strlen(n)); + } else { + if (p != str) + p--; // remove tailing SDB_RS + *p = 0; + p[1] = 0; + } + sdb_set_owned(s, key, str, cas); + return 1; +} + +// XXX Doesnt work if numbers are stored in different base +RZ_API bool sdb_array_contains_num(Sdb *s, const char *key, ut64 num, ut32 *cas) { + char val[SDB_NUM_BUFSZ]; + char *nval = sdb_itoa(num, val, SDB_NUM_BASE); + return sdb_array_contains(s, key, nval, cas); +} + +RZ_API bool sdb_array_contains(Sdb *s, const char *key, const char *val, ut32 *cas) { + if (!s || !key || !val) { + return false; + } + const char *next, *ptr = sdb_const_get(s, key, cas); + if (ptr && *ptr) { + size_t vlen = strlen(val); + while (1) { + next = strchr(ptr, SDB_RS); + size_t len = next ? (size_t)(next - ptr) : strlen(ptr); + if (len == vlen && !memcmp(ptr, val, len)) { + return true; + } + if (!next) { + break; + } + ptr = next + 1; + } + } + return false; +} + +RZ_API int sdb_array_size(Sdb *s, const char *key) { + return sdb_alen(sdb_const_get(s, key, 0)); +} + +// NOTE: ignore empty buckets +RZ_API int sdb_array_length(Sdb *s, const char *key) { + return sdb_alen_ignore_empty(sdb_const_get(s, key, 0)); +} + +RZ_API int sdb_array_push_num(Sdb *s, const char *key, ut64 num, ut32 cas) { + char buf[SDB_NUM_BUFSZ], *n = sdb_itoa(num, buf, SDB_NUM_BASE); + return sdb_array_push(s, key, n, cas); +} + +RZ_API bool sdb_array_push(Sdb *s, const char *key, const char *val, ut32 cas) { +#if PUSH_PREPENDS + return sdb_array_prepend(s, key, val, cas); +#else + return sdb_array_append(s, key, val, cas); +#endif +} + +RZ_API bool sdb_array_prepend_num(Sdb *s, const char *key, ut64 num, ut32 cas) { + char buf[SDB_NUM_BUFSZ]; + char *n = sdb_itoa(num, buf, SDB_NUM_BASE); + return sdb_array_push(s, key, n, cas); +} + +RZ_API bool sdb_array_prepend(Sdb *s, const char *key, const char *val, ut32 cas) { + if (!s || !key || !val) { + return false; + } + int str_len = 0; + ut32 kas = cas; + const char *str = sdb_const_get_len(s, key, &str_len, &kas); + if (!val || (cas && cas != kas)) { + return false; + } + cas = kas; + if (str && *str) { + int val_len = strlen(val); + char *newval = malloc(str_len + val_len + 2); + if (!newval) { + return false; + } + memcpy(newval, val, val_len); + newval[val_len] = SDB_RS; + memcpy(newval + val_len + 1, str, str_len); + newval[str_len + val_len + 1] = 0; + // TODO: optimize this because we already have allocated and strlened everything + sdb_set_owned(s, key, newval, cas); + } else { + sdb_set(s, key, val, cas); + } + return true; +} + +RZ_API ut64 sdb_array_pop_num(Sdb *s, const char *key, ut32 *cas) { + ut64 ret; + char *a = sdb_array_pop(s, key, cas); + if (!a) { + if (cas) { + *cas = UT32_MAX; // invalid + } + return UT64_MAX; + } + if (cas) { + *cas = 0; + } + ret = sdb_atoi(a); + free(a); + return ret; +} + +RZ_API char *sdb_array_pop(Sdb *s, const char *key, ut32 *cas) { +#if PUSH_PREPENDS + return sdb_array_pop_head(s, key, cas); +#else + return sdb_array_pop_tail(s, key, cas); +#endif +} + +RZ_API char *sdb_array_pop_head(Sdb *s, const char *key, ut32 *cas) { + // remove last element in + ut32 kas; + char *end, *str = sdb_get(s, key, &kas); + if (!str || !*str) { + free(str); + return NULL; + } + if (cas && *cas != kas) { + *cas = kas; + } + end = strchr(str, SDB_RS); + if (end) { + *end = 0; + sdb_set(s, key, end + 1, 0); + } else { + sdb_unset(s, key, 0); + } + return str; +} + +RZ_API char *sdb_array_pop_tail(Sdb *s, const char *key, ut32 *cas) { + ut32 kas; + char *end, *str = sdb_get(s, key, &kas); + if (!str || !*str) { + free(str); + return NULL; + } + if (cas && *cas != kas) { + *cas = kas; + } + for (end = str + strlen(str) - 1; end > str && *end != SDB_RS; end--) { + // nothing to see here + } + if (*end == SDB_RS) { + *end++ = 0; + } + sdb_set_owned(s, key, str, 0); + // XXX: probably wrong + return strdup(end); +} + +RZ_API void sdb_array_sort(Sdb *s, const char *key, ut32 cas) { + char *nstr, *str, **strs; + int lstr, j, i; + str = sdb_get_len(s, key, &lstr, 0); + if (!str) { + return; + } + if (!*str) { + free(str); + return; + } + strs = sdb_fmt_array(str); + for (i = 0; strs[i]; i++) { + // nothing to see here + } + qsort(strs, i, sizeof(char *), cstring_cmp); + nstr = str; + for (i = 0; strs[i]; i++) { + j = strlen(strs[i]); + memcpy(nstr, strs[i], j); + nstr += j; + *(nstr++) = SDB_RS; + } + if (nstr > str) { + *(--nstr) = '\0'; + } else { + *nstr = '\0'; + } + sdb_set_owned(s, key, str, cas); + free(strs); +} + +RZ_API void sdb_array_sort_num(Sdb *s, const char *key, ut32 cas) { + char *ret, *nstr; + + char *str = sdb_get(s, key, 0); + if (!str) { + return; + } + if (!*str) { + free(str); + return; + } + ut64 *nums = sdb_fmt_array_num(str); + free(str); + if (!nums) { + return; + } + + qsort(nums + 1, (int)*nums, sizeof(ut64), int_cmp); + + nstr = malloc(*nums + 1); + if (!nstr) { + free(nums); + return; + } + memset(nstr, 'q', *nums); + nstr[*nums] = '\0'; + + ret = sdb_fmt_tostr(nums + 1, nstr); + sdb_set_owned(s, key, ret, cas); + + free(nstr); + free(nums); + return; +} diff --git a/librz/util/sdb/src/base64.c b/librz/util/sdb/src/base64.c new file mode 100644 index 00000000000..ffc81bd1c91 --- /dev/null +++ b/librz/util/sdb/src/base64.c @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2011-2016 pancake +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include "sdb.h" + +RZ_API void sdb_encode_raw(char *bout, const ut8 *bin, int len) { + rz_base64_encode(bout, bin, len); +} + +RZ_API int sdb_decode_raw(ut8 *bout, const char *bin, int len) { + return rz_base64_decode(bout, bin, len); +} + +RZ_API char *sdb_encode(const ut8 *bin, int len) { + char *out; + if (!bin) { + return NULL; + } + if (len < 0) { + len = strlen((const char *)bin); + } + if (!len) { + return strdup(""); + } + out = calloc(8 + (len * 2), sizeof(char)); + if (!out) { + return NULL; + } + sdb_encode_raw(out, bin, len); + return out; +} + +RZ_API ut8 *sdb_decode(const char *in, int *len) { + ut8 *out; + ut32 size; + int olen, ilen; + if (len) { + *len = 0; + } + if (!in) { + return NULL; + } + ilen = strlen(in); + if (!ilen) { + return NULL; + } + size = (ilen * 3) + 16; + if (size < (ut32)ilen) { + return NULL; + } + out = calloc(1, size); + if (!out) { + return NULL; + } + olen = sdb_decode_raw(out, in, ilen); + if (!olen) { + free(out); + return NULL; + } + out[olen] = 0; + if (len) { + *len = olen; + } + return out; +} diff --git a/librz/util/sdb/src/buffer.c b/librz/util/sdb/src/buffer.c new file mode 100644 index 00000000000..ba977ee6698 --- /dev/null +++ b/librz/util/sdb/src/buffer.c @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: unknown +// SPDX-License-Identifier: CC-PDDC + +#include "buffer.h" + +void buffer_init(buffer *s, BufferOp op, int fd, char *buf, ut32 len) { + s->x = buf; + s->fd = fd; + s->op = op; + s->p = 0; + s->n = len; +} + +static int allwrite(BufferOp op, int fd, const char *buf, ut32 len) { + ut32 w; + while (len > 0) { + w = op(fd, buf, len); + if (w != len) { + return 0; + } + buf += w; + len -= w; + } + return 1; +} + +int buffer_flush(buffer *s) { + int p = s->p; + if (!p) { + return 1; + } + s->p = 0; + return allwrite(s->op, s->fd, s->x, p); +} + +int buffer_putalign(buffer *s, const char *buf, ut32 len) { + ut32 n; + if (!s || !s->x || !buf) { + return 0; + } + while (len > (n = s->n - s->p)) { + memcpy(s->x + s->p, buf, n); + s->p += n; + buf += n; + len -= n; + if (!buffer_flush(s)) { + return 0; + } + } + /* now len <= s->n - s->p */ + memcpy(s->x + s->p, buf, len); + s->p += len; + return 1; +} + +int buffer_putflush(buffer *s, const char *buf, ut32 len) { + if (!buffer_flush(s)) { + return 0; + } + return allwrite(s->op, s->fd, buf, len); +} diff --git a/librz/util/sdb/src/buffer.h b/librz/util/sdb/src/buffer.h new file mode 100644 index 00000000000..83e1d6c7fae --- /dev/null +++ b/librz/util/sdb/src/buffer.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: unknown +// SPDX-License-Identifier: CC-PDDC + +#ifndef BUFFER_H +#define BUFFER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int (*BufferOp)(int, const char *, int); + +typedef struct buffer { + char *x; + unsigned int p; + unsigned int n; + int fd; + BufferOp op; +} buffer; + +#define BUFFER_INIT(op, fd, buf, len) \ + { (buf), 0, (len), (fd), (op) } +#define BUFFER_INSIZE 8192 +#define BUFFER_OUTSIZE 8192 + +extern void buffer_init(buffer *, BufferOp, int, char *, unsigned int); + +extern int buffer_flush(buffer *); +extern int buffer_put(buffer *, const char *, unsigned int); +extern int buffer_putalign(buffer *, const char *, unsigned int); +extern int buffer_putflush(buffer *, const char *, unsigned int); + +#define buffer_PUTC(s, c) \ + (((s)->n != (s)->p) \ + ? ((s)->x[(s)->p++] = (c), 0) \ + : buffer_put((s), &(c), 1)) + +extern int buffer_get(buffer *, char *, unsigned int); +extern int buffer_bget(buffer *, char *, unsigned int); +extern int buffer_feed(buffer *); + +extern char *buffer_peek(buffer *); +extern void buffer_seek(buffer *, unsigned int); + +#define buffer_PEEK(s) ((s)->x + (s)->n) +#define buffer_SEEK(s, len) (((s)->p -= (len)), ((s)->n += (len))) + +#define buffer_GETC(s, c) \ + (((s)->p > 0) \ + ? (*(c) = (s)->x[(s)->n], buffer_SEEK((s), 1), 1) \ + : buffer_get((s), (c), 1)) + +extern int buffer_copy(buffer *, buffer *); + +extern buffer *buffer_0; +extern buffer *buffer_0small; +extern buffer *buffer_1; +extern buffer *buffer_1small; +extern buffer *buffer_2; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librz/util/sdb/src/cdb.c b/librz/util/sdb/src/cdb.c new file mode 100644 index 00000000000..6e6fb1dd8c4 --- /dev/null +++ b/librz/util/sdb/src/cdb.c @@ -0,0 +1,194 @@ +// SPDX-FileCopyrightText: D. J. Bernstein +// SPDX-FileCopyrightText: 2014-2016 pancake +// SPDX-License-Identifier: CC-PDDC + +#include +#include +#include +#include "cdb.h" +#if HAVE_HEADER_SYS_MMAN_H +#include +#endif +#include "sdb_private.h" + +/* XXX: this code must be rewritten . too slow */ +bool cdb_getkvlen(struct cdb *c, ut32 *klen, ut32 *vlen, ut32 pos) { + ut8 buf[4] = { 0 }; + *klen = *vlen = 0; + if (!cdb_read(c, (char *)buf, sizeof(buf), pos)) { + return false; + } + *klen = (ut32)buf[0]; + *vlen = (ut32)(buf[1] | ((ut32)buf[2] << 8) | ((ut32)buf[3] << 16)); + if (*vlen > CDB_MAX_VALUE) { + *vlen = CDB_MAX_VALUE; // untaint value for coverity + return false; + } + return true; +} + +void cdb_free(struct cdb *c) { + if (!c->map) { + return; + } +#if HAVE_HEADER_SYS_MMAN_H + (void)munmap(c->map, c->size); +#else + free(c->map); +#endif + c->map = NULL; +} + +void cdb_findstart(struct cdb *c) { + c->loop = 0; +#if !HAVE_HEADER_SYS_MMAN_H + if (c->fd != -1) { + lseek(c->fd, 0, SEEK_SET); + } +#endif +} + +bool cdb_init(struct cdb *c, int fd) { + struct stat st; + if (fd != c->fd && c->fd != -1) { + close(c->fd); + } + c->fd = fd; + cdb_findstart(c); + if (fd != -1 && !fstat(fd, &st) && st.st_size > 4 && st.st_size != (off_t)UT64_MAX) { +#if HAVE_HEADER_SYS_MMAN_H + char *x = mmap(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (x == MAP_FAILED) { + eprintf("Cannot mmap %d\n", (int)st.st_size); + return false; + } + if (c->map) { + munmap(c->map, c->size); + } +#else + char *x = calloc(1, st.st_size); + if (!x) { + eprintf("Cannot malloc %d\n", (int)st.st_size); + return false; + } + /* TODO: read by chunks instead of a big huge syscall */ + if (read(fd, x, st.st_size) != st.st_size) { + /* handle read error */ + } + free(c->map); +#endif + c->map = x; + c->size = st.st_size; + return true; + } + c->map = NULL; + c->size = 0; + return false; +} + +bool cdb_read(struct cdb *c, char *buf, ut32 len, ut32 pos) { + if (c->map) { + if ((pos > c->size) || (c->size - pos < len)) { + return false; + } + if (!buf) { + return false; + } + memcpy(buf, c->map + pos, len); + return true; + } + if (c->fd == -1 || !seek_set(c->fd, pos)) { + return false; + } + while (len > 0) { + int r = (int)read(c->fd, buf, len); + if (r < 1 || (ut32)r != len) { + return false; + } + buf += r; + len -= r; + } + return true; +} + +static int match(struct cdb *c, const char *key, ut32 len, ut32 pos) { + char buf[32]; + const size_t szb = sizeof buf; + while (len > 0) { + int n = (szb > len) ? len : szb; + if (!cdb_read(c, buf, n, pos)) { + return -1; + } + if (memcmp(buf, key, n)) { + return 0; + } + pos += n; + key += n; + len -= n; + } + return 1; +} + +int cdb_findnext(struct cdb *c, ut32 u, const char *key, ut32 len) { + char buf[8]; + ut32 pos; + int m; + len++; + if (c->fd == -1) { + return -1; + } + c->hslots = 0; + if (!c->loop) { + const int bufsz = ((u + 1) & 0xFF) ? sizeof(buf) : sizeof(buf) / 2; + if (!cdb_read(c, buf, bufsz, (u << 2) & 1023)) { + return -1; + } + /* hslots = (hpos_next - hpos) / 8 */ + ut32_unpack(buf, &c->hpos); + if (bufsz == sizeof(buf)) { + ut32_unpack(buf + 4, &pos); + } else { + pos = c->size; + } + if (pos < c->hpos) { + return -1; + } + c->hslots = (pos - c->hpos) / (2 * sizeof(ut32)); + if (!c->hslots) { + return 0; + } + c->khash = u; + u = ((u >> 8) % c->hslots) << 3; + c->kpos = c->hpos + u; + } + while (c->loop < c->hslots) { + if (!cdb_read(c, buf, sizeof(buf), c->kpos)) { + return 0; + } + ut32_unpack(buf + 4, &pos); + if (!pos) { + return 0; + } + c->loop++; + c->kpos += sizeof(buf); + if (c->kpos == c->hpos + (c->hslots << 3)) { + c->kpos = c->hpos; + } + ut32_unpack(buf, &u); + if (u == c->khash) { + if (!cdb_getkvlen(c, &u, &c->dlen, pos) || !u) { + return -1; + } + if (u == len) { + if ((m = match(c, key, len, pos + KVLSZ)) == -1) { + return 0; + } + if (m == 1) { + c->dpos = pos + KVLSZ + len; + return 1; + } + } + } + } + return 0; +} diff --git a/librz/util/sdb/src/cdb.h b/librz/util/sdb/src/cdb.h new file mode 100644 index 00000000000..b54930edd06 --- /dev/null +++ b/librz/util/sdb/src/cdb.h @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: D. J. Bernstein +// SPDX-License-Identifier: CC-PDDC + +#ifndef CDB_H +#define CDB_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define KVLSZ 4 +#define CDB_MAX_KEY 0xff +#define CDB_MAX_VALUE 0xffffff + +#define CDB_HASHSTART 5381 + +struct cdb { + char *map; /* 0 if no map is available */ + int fd; /* filedescriptor */ + ut32 size; /* initialized if map is nonzero */ + ut32 loop; /* number of hash slots searched under this key */ + ut32 khash; /* initialized if loop is nonzero */ + ut32 kpos; /* initialized if loop is nonzero */ + ut32 hpos; /* initialized if loop is nonzero */ + ut32 hslots; /* initialized if loop is nonzero */ + ut32 dpos; /* initialized if cdb_findnext() returns 1 */ + ut32 dlen; /* initialized if cdb_findnext() returns 1 */ +}; + +/* TODO THIS MUST GTFO! */ +bool cdb_getkvlen(struct cdb *db, ut32 *klen, ut32 *vlen, ut32 pos); +void cdb_free(struct cdb *); +bool cdb_init(struct cdb *, int fd); +void cdb_findstart(struct cdb *); +bool cdb_read(struct cdb *, char *, unsigned int, ut32); +int cdb_findnext(struct cdb *, ut32 u, const char *, ut32); + +#define cdb_datapos(c) ((c)->dpos) +#define cdb_datalen(c) ((c)->dlen) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librz/util/sdb/src/cdb_make.c b/librz/util/sdb/src/cdb_make.c new file mode 100644 index 00000000000..81561e18918 --- /dev/null +++ b/librz/util/sdb/src/cdb_make.c @@ -0,0 +1,196 @@ +// SPDX-FileCopyrightText: D. J. Bernstein +// SPDX-License-Identifier: CC-PDDC + +#include +#include +#include "sdb.h" +#include "cdb.h" +#include "cdb_make.h" +#include "sdb_private.h" + +#if __APPLE__ +// for MAC_OS_X_VERSION_10_6 +#include +#endif + +#define ALIGNMENT sizeof(void *) + +char *cdb_alloc(ut32 n) { +#if __APPLE__ && defined(MAC_OS_X_VERSION_10_6) + void *ret = NULL; + return posix_memalign(&ret, ALIGNMENT, n) ? NULL : ret; +#elif __WINDOWS__ && !__CYGWIN__ + return _aligned_malloc(n, ALIGNMENT); +#else + return malloc(n); +#endif +} + +void cdb_alloc_free(void *x) { +#if __WINDOWS__ && !__CYGWIN__ + _aligned_free(x); +#else + free(x); +#endif +} + +int cdb_make_start(struct cdb_make *c, int fd) { + int i; + c->head = 0; + c->split = 0; + c->hash = 0; + c->numentries = 0; + c->fd = fd; + c->pos = sizeof(c->final); + buffer_init(&c->b, (BufferOp)write, fd, c->bspace, sizeof(c->bspace)); + c->memsize = 1; + for (i = 0; i < 256; i++) { + c->count[i] = 0; + } + return seek_set(fd, c->pos); +} + +static inline int incpos(struct cdb_make *c, ut32 len) { + ut32 newpos = c->pos + len; + if (newpos < len) { + return 0; + } + c->pos = newpos; + return 1; +} + +#define R_ANEW(x) (x *)cdb_alloc(sizeof(x)) +int cdb_make_addend(struct cdb_make *c, ut32 keylen, ut32 datalen, ut32 h) { + ut32 u; + struct cdb_hplist *head = c->head; + if (!head || (head->num >= CDB_HPLIST)) { + if (!(head = R_ANEW(struct cdb_hplist))) { + return 0; + } + head->num = 0; + head->next = c->head; + c->head = head; + } + head->hp[head->num].h = h; + head->hp[head->num].p = c->pos; + head->num++; + c->numentries++; + c->count[255 & h]++; + u = c->count[255 & h] * 2; + if (u > c->memsize) { + c->memsize = u; + } + return incpos(c, KVLSZ + keylen + datalen); +} + +static int pack_kvlen(ut8 *buf, ut32 klen, ut32 vlen) { + if (klen > SDB_CDB_MAX_KEY) { + return 0; // 0xff = 254 chars+trailing zero + } + if (vlen >= SDB_CDB_MAX_VALUE) { + return 0; + } + buf[0] = (ut8)klen; + buf[1] = (ut8)((vlen)&0xff); + buf[2] = (ut8)((vlen >> 8) & 0xff); + buf[3] = (ut8)((vlen >> 16) & 0xff); + return 1; +} + +int cdb_make_addbegin(struct cdb_make *c, ut32 keylen, ut32 datalen) { + ut8 buf[KVLSZ]; + if (!pack_kvlen(buf, keylen, datalen)) { + return 0; + } + return buffer_putalign(&c->b, (const char *)buf, KVLSZ); +} + +int cdb_make_add(struct cdb_make *c, const char *key, ut32 keylen, const char *data, ut32 datalen) { + /* add tailing \0 to allow mmap to work later */ + keylen++; + datalen++; + if (!cdb_make_addbegin(c, keylen, datalen)) { + return 0; + } + if (!buffer_putalign(&c->b, key, keylen)) { + return 0; + } + if (!buffer_putalign(&c->b, data, datalen)) { + return 0; + } + return cdb_make_addend(c, keylen, datalen, sdb_hash(key)); +} + +int cdb_make_finish(struct cdb_make *c) { + int i; + char buf[8]; + struct cdb_hp *hp; + struct cdb_hplist *x, *n; + ut32 len, u, memsize, count, where; + + memsize = c->memsize + c->numentries; + if (memsize > (UT32_MAX / sizeof(struct cdb_hp))) { + return 0; + } + c->split = (struct cdb_hp *)cdb_alloc(memsize * sizeof(struct cdb_hp)); + if (!c->split) { + return 0; + } + c->hash = c->split + c->numentries; + + for (u = i = 0; i < 256; i++) { + u += c->count[i]; /* bounded by numentries, so no overflow */ + c->start[i] = u; + } + + for (x = c->head; x; x = x->next) { + i = x->num; + while (i--) { + c->split[--c->start[255 & x->hp[i].h]] = x->hp[i]; + } + } + + for (i = 0; i < 256; i++) { + count = c->count[i]; + len = count << 1; + ut32_pack(c->final + 4 * i, c->pos); + for (u = 0; u < len; u++) { + c->hash[u].h = c->hash[u].p = 0; + } + hp = c->split + c->start[i]; + for (u = 0; u < count; u++) { + where = (hp->h >> 8) % len; + while (c->hash[where].p) { + if (++where == len) { + where = 0; + } + } + c->hash[where] = *hp++; + } + for (u = 0; u < len; u++) { + ut32_pack(buf, c->hash[u].h); + ut32_pack(buf + 4, c->hash[u].p); + if (!buffer_putalign(&c->b, buf, 8)) { + return 0; + } + if (!incpos(c, 8)) { + return 0; + } + } + } + + if (!buffer_flush(&c->b)) { + return 0; + } + if (!seek_set(c->fd, 0)) { + return 0; + } + // free childs + for (x = c->head; x;) { + n = x->next; + cdb_alloc_free(x); + x = n; + } + cdb_alloc_free(c->split); + return buffer_putflush(&c->b, c->final, sizeof c->final); +} diff --git a/librz/util/sdb/src/cdb_make.h b/librz/util/sdb/src/cdb_make.h new file mode 100644 index 00000000000..48273ae38bc --- /dev/null +++ b/librz/util/sdb/src/cdb_make.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: D. J. Bernstein +// SPDX-License-Identifier: CC-PDDC + +#ifndef CDB_MAKE_H +#define CDB_MAKE_H + +#include "buffer.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define CDB_HPLIST 1000 + +struct cdb_hp { + ut32 h; + ut32 p; +}; + +struct cdb_hplist { + struct cdb_hp hp[CDB_HPLIST]; + struct cdb_hplist *next; + int num; +}; + +struct cdb_make { + char bspace[8192]; + char final[1024]; + ut32 count[256]; + ut32 start[256]; + struct cdb_hplist *head; + struct cdb_hp *split; /* includes space for hash */ + struct cdb_hp *hash; + ut32 numentries; + ut32 memsize; + buffer b; + ut32 pos; + int fd; +}; + +extern int cdb_make_start(struct cdb_make *, int); +extern int cdb_make_addbegin(struct cdb_make *, unsigned int, unsigned int); +extern int cdb_make_addend(struct cdb_make *, unsigned int, unsigned int, ut32); +extern int cdb_make_add(struct cdb_make *, const char *, unsigned int, const char *, unsigned int); +extern int cdb_make_finish(struct cdb_make *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librz/util/sdb/src/diff.c b/librz/util/sdb/src/diff.c new file mode 100644 index 00000000000..ec777ce57fc --- /dev/null +++ b/librz/util/sdb/src/diff.c @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: 2019 thestr4ng3r +// SPDX-License-Identifier: MIT + +#include "sdb.h" + +RZ_API int sdb_diff_format(char *str, int size, const SdbDiff *diff) { + int r = 0; +#define APPENDF(...) \ + do { \ + int sr = snprintf(str, size, __VA_ARGS__); \ + if (sr < 0) { \ + return sr; \ + } \ + r += sr; \ + if (sr >= size) { \ + /* no space left, only measure from now on */ \ + str = NULL; \ + size = 0; \ + } else { \ + str += sr; \ + size -= sr; \ + } \ + } while (0) + + APPENDF("%c%s ", diff->add ? '+' : '-', diff->v ? " " : "NS"); + + SdbListIter *it; + const char *component; + ls_foreach (diff->path, it, component) { + APPENDF("%s/", component); + } + + if (diff->v) { + APPENDF("%s=%s", diff->k, diff->v); + } else { + APPENDF("%s", diff->k); + } + +#undef APPENDF + return r; +} + +typedef struct sdb_diff_ctx_t { + Sdb *a; + Sdb *b; + bool equal; + SdbList *path; + SdbDiffCallback cb; + void *cb_user; +} SdbDiffCtx; + +#define DIFF(ctx, c, ret) \ + do { \ + (ctx)->equal = false; \ + if ((ctx)->cb) { \ + c \ + } else { \ + /* we already know it's not equal and don't care about the rest of the diff */ \ + return ret; \ + } \ + } while (0) + +static void sdb_diff_report_ns(SdbDiffCtx *ctx, SdbNs *ns, bool add) { + SdbDiff diff = { ctx->path, ns->name, NULL, add }; + ctx->cb(&diff, ctx->cb_user); +} + +static void sdb_diff_report_kv(SdbDiffCtx *ctx, const char *k, const char *v, bool add) { + SdbDiff diff = { ctx->path, k, v, add }; + ctx->cb(&diff, ctx->cb_user); +} + +typedef struct sdb_diff_kv_cb_ctx { + SdbDiffCtx *ctx; + bool add; +} SdbDiffKVCbCtx; + +static bool sdb_diff_report_kv_cb(void *user, const char *k, const char *v) { + const SdbDiffKVCbCtx *ctx = user; + sdb_diff_report_kv(ctx->ctx, k, v, ctx->add); + return true; +} + +/** + * just report everything from sdb to buf with prefix + */ +static void sdb_diff_report(SdbDiffCtx *ctx, Sdb *sdb, bool add) { + SdbListIter *it; + SdbNs *ns; + ls_foreach (sdb->ns, it, ns) { + sdb_diff_report_ns(ctx, ns, add); + ls_push(ctx->path, ns->name); + sdb_diff_report(ctx, ns->sdb, add); + ls_pop(ctx->path); + } + SdbDiffKVCbCtx cb_ctx = { ctx, add }; + sdb_foreach(sdb, sdb_diff_report_kv_cb, &cb_ctx); +} + +static bool sdb_diff_kv_cb(void *user, const char *k, const char *v) { + const SdbDiffKVCbCtx *ctx = user; + Sdb *other = ctx->add ? ctx->ctx->a : ctx->ctx->b; + const char *other_val = sdb_const_get(other, k, NULL); + if (!other_val || !*other_val) { + DIFF(ctx->ctx, + sdb_diff_report_kv(ctx->ctx, k, v, ctx->add); + , false); + } else if (!ctx->add && strcmp(v, other_val) != 0) { + DIFF(ctx->ctx, + sdb_diff_report_kv(ctx->ctx, k, v, false); + sdb_diff_report_kv(ctx->ctx, k, other_val, true); + , false); + } + return true; +} + +static void sdb_diff_ctx(SdbDiffCtx *ctx) { + SdbListIter *it; + SdbNs *ns; + ls_foreach (ctx->a->ns, it, ns) { + Sdb *b_ns = sdb_ns(ctx->b, ns->name, false); + if (!b_ns) { + DIFF(ctx, + sdb_diff_report_ns(ctx, ns, false); + ls_push(ctx->path, ns->name); + sdb_diff_report(ctx, ns->sdb, false); + ls_pop(ctx->path); + , ); + continue; + } + Sdb *a = ctx->a; + Sdb *b = ctx->b; + ctx->a = ns->sdb; + ctx->b = b_ns; + ls_push(ctx->path, ns->name); + sdb_diff_ctx(ctx); + ls_pop(ctx->path); + ctx->a = a; + ctx->b = b; + } + ls_foreach (ctx->b->ns, it, ns) { + if (!sdb_ns(ctx->a, ns->name, false)) { + DIFF(ctx, + sdb_diff_report_ns(ctx, ns, true); + ls_push(ctx->path, ns->name); + sdb_diff_report(ctx, ns->sdb, true); + ls_pop(ctx->path); + , ); + } + } + SdbDiffKVCbCtx kv_ctx = { ctx, false }; + if (!sdb_foreach(ctx->a, sdb_diff_kv_cb, &kv_ctx)) { + return; + } + kv_ctx.add = true; + sdb_foreach(ctx->b, sdb_diff_kv_cb, &kv_ctx); +} + +RZ_API bool sdb_diff(Sdb *a, Sdb *b, SdbDiffCallback cb, void *cb_user) { + SdbDiffCtx ctx; + ctx.a = a; + ctx.b = b; + ctx.equal = true; + ctx.cb = cb; + ctx.cb_user = cb_user; + ctx.path = ls_new(); + if (!ctx.path) { + return false; + } + sdb_diff_ctx(&ctx); + ls_free(ctx.path); + return ctx.equal; +} diff --git a/librz/util/sdb/src/disk.c b/librz/util/sdb/src/disk.c new file mode 100644 index 00000000000..01cf8cb9207 --- /dev/null +++ b/librz/util/sdb/src/disk.c @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2013-2018 pancake +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sdb.h" + +RZ_API bool sdb_disk_create(Sdb *s) { + int nlen; + char *str; + const char *dir; + if (!s || s->fdump >= 0) { + return false; // cannot re-create + } + if (!s->dir && s->name) { + s->dir = strdup(s->name); + } + dir = s->dir ? s->dir : "./"; + RZ_FREE(s->ndump); + nlen = strlen(dir); + str = malloc(nlen + 5); + if (!str) { + return false; + } + memcpy(str, dir, nlen + 1); + char *dirname = rz_file_dirname(str); + if (!dirname) { + free(str); + return false; + } + rz_sys_mkdirp(dirname); + free(dirname); + memcpy(str + nlen, ".tmp", 5); + if (s->fdump != -1) { + close(s->fdump); + } +#if __WINDOWS__ + wchar_t *wstr = rz_utf8_to_utf16(str); + if (wstr) { + s->fdump = _wopen(wstr, O_BINARY | O_RDWR | O_CREAT | O_TRUNC, SDB_MODE); + free(wstr); + } else { + s->fdump = -1; + } +#else + s->fdump = open(str, O_BINARY | O_RDWR | O_CREAT | O_TRUNC, SDB_MODE); +#endif + if (s->fdump == -1) { + eprintf("sdb: Cannot open '%s' for writing.\n", str); + free(str); + return false; + } + cdb_make_start(&s->m, s->fdump); + s->ndump = str; + return true; +} + +RZ_API bool sdb_disk_insert(Sdb *s, const char *key, const char *val) { + struct cdb_make *c = &s->m; + if (!key || !val) { + return false; + } + // if (!*val) return 0; //undefine variable if no value + return cdb_make_add(c, key, strlen(key), val, strlen(val)); +} + +#define IFRET(x) \ + if (x) \ + ret = 0 +RZ_API bool sdb_disk_finish(Sdb *s) { + bool reopen = false, ret = true; + IFRET(!cdb_make_finish(&s->m)); +#if HAVE_HEADER_SYS_MMAN_H + IFRET(fsync(s->fdump)); +#endif + IFRET(close(s->fdump)); + s->fdump = -1; + // close current fd to avoid sharing violations + if (s->fd != -1) { + close(s->fd); + s->fd = -1; + reopen = true; + } +#if __WINDOWS__ + wchar_t *ndump_ = rz_utf8_to_utf16(s->ndump); + wchar_t *dir_ = rz_utf8_to_utf16(s->dir); + + if (!MoveFileExW(ndump_, dir_, MOVEFILE_REPLACE_EXISTING)) { + rz_sys_perror("MoveFileExW SDB file to finale location"); + } + free(ndump_); + free(dir_); +#else + if (s->ndump && s->dir) { + IFRET(rename(s->ndump, s->dir)); + } +#endif + free(s->ndump); + s->ndump = NULL; + // reopen if was open before + reopen = true; // always reopen if possible + if (reopen) { + int rr = sdb_open(s, s->dir); + if (ret && rr < 0) { + ret = false; + } + cdb_init(&s->db, s->fd); + } + return ret; +} + +RZ_API bool sdb_disk_unlink(Sdb *s) { + return (s->dir && *(s->dir) && unlink(s->dir) != -1); +} diff --git a/librz/util/sdb/src/fmt.c b/librz/util/sdb/src/fmt.c new file mode 100644 index 00000000000..5336556628a --- /dev/null +++ b/librz/util/sdb/src/fmt.c @@ -0,0 +1,232 @@ +// SPDX-FileCopyrightText: 2014-2018 pancake +// SPDX-License-Identifier: MIT + +#include "sdb.h" +#include +#include + +// TODO: convert into a function +// TODO: Add 'a' format for array of pointers null terminated?? +// XXX SLOW CONCAT +#define concat(x) \ + if (x) { \ + int size = 2 + strlen(x ? x : "") + (out ? strlen(out) + 4 : 0); \ + if (out) { \ + char *o = realloc(out, size); \ + if (o) { \ + strcat(o, ","); \ + strcat(o, x); \ + out = o; \ + } \ + } else { \ + out = strdup(x); \ + } \ + } + +RZ_API char *sdb_fmt(const char *fmt, ...) { +#define KL 256 +#define KN 16 + static char Key[KN][KL]; + static int n = 0; + va_list ap; + va_start(ap, fmt); + n = (n + 1) % KN; + if (fmt) { + *Key[n] = 0; + vsnprintf(Key[n], KL - 1, fmt, ap); + Key[n][KL - 1] = 0; + } + va_end(ap); + return Key[n]; +} + +RZ_API char *sdb_fmt_tostr(void *p, const char *fmt) { + char buf[128], *e_str, *out = NULL; + int n, len = 0; + if (!p || !fmt) { + return NULL; + } + for (; *fmt; fmt++) { + n = 4; + switch (*fmt) { + case 'b': + concat(sdb_itoa((ut64) * ((ut8 *)p + len), buf, 10)); + break; + case 'h': + concat(sdb_itoa((ut64) * ((short *)((ut8 *)p + len)), buf, 10)); + break; + case 'd': + concat(sdb_itoa((ut64) * ((int *)((ut8 *)p + len)), buf, 10)); + break; + case 'q': + concat(sdb_itoa(*((ut64 *)((ut8 *)p + len)), buf, 10)); + n = 8; + break; + case 'z': + concat((char *)p + len); + break; + case 's': + e_str = sdb_encode((const ut8 *)*((char **)((ut8 *)p + len)), -1); + concat(e_str); + free(e_str); + break; + case 'p': + concat(sdb_itoa((ut64) * ((size_t *)((ut8 *)p + len)), buf, 16)); + n = sizeof(size_t); + break; + } + len += RZ_MAX((long)sizeof(void *), n); // align + } + return out; +} + +// TODO: return false if array length != fmt length +RZ_API int sdb_fmt_tobin(const char *_str, const char *fmt, void *stru) { + int n, idx = 0, items = 0; + char *stru8 = (char *)stru; + char *next, *str, *ptr, *word, *e_str; + if (!_str || !*_str || !fmt) { + return 0; + } + str = ptr = strdup(_str); + for (; *fmt; fmt++) { + word = sdb_anext(ptr, &next); + if (!word || !*word) { + break; + } + items++; + n = 4; // ALIGN + switch (*fmt) { + case 'b': *((ut8 *)(stru8 + idx)) = (ut8)sdb_atoi(word); break; + case 'd': *((int *)(stru8 + idx)) = (int)sdb_atoi(word); break; + case 'q': + *((ut64 *)(stru8 + idx)) = sdb_atoi(word); + n = 8; + break; + case 'h': *((short *)(stru8 + idx)) = (short)sdb_atoi(word); break; + case 's': + e_str = (char *)sdb_decode(word, 0); + *((char **)(stru8 + idx)) = e_str ? e_str : strdup(word); + break; + case 'z': + *((char **)(stru8 + idx)) = (char *)strdup(word); + break; + case 'p': + *((void **)(stru8 + idx)) = (void *)(size_t)sdb_atoi(word); + break; + } + idx += RZ_MAX((long)sizeof(void *), n); // align + if (!next) { + break; + } + ptr = next; + } + free(str); + return items; +} + +RZ_API void sdb_fmt_free(void *stru, const char *fmt) { + int n, len = 0; + for (; *fmt; fmt++) { + n = 4; + switch (*fmt) { + case 'p': // TODO: leak or wat + case 'b': + case 'h': + case 'd': + /* do nothing */ + break; + case 'q': + n = 8; + break; + case 'z': + case 's': + free((void *)*((char **)((ut8 *)stru + len))); + break; + } + len += RZ_MAX((long)sizeof(void *), n); // align + } +} + +RZ_API int sdb_fmt_init(void *p, const char *fmt) { + int len = 0; + for (; *fmt; fmt++) { + switch (*fmt) { + case 'b': len += sizeof(ut8); break; // 1 + case 'h': len += sizeof(short); break; // 2 + case 'd': len += sizeof(ut32); break; // 4 + case 'q': len += sizeof(ut64); break; // 8 + case 'z': len += sizeof(char *); break; // void* + case 's': len += sizeof(char *); break; // void* + case 'p': len += sizeof(char *); break; // void * + } + } + if (p) { + memset(p, 0, len); + } + return len; +} + +static const char *sdb_anext2(const char *str, const char **next) { + char *nxt, *p = strchr(str, SDB_RS); + if (p) { + nxt = p + 1; + } else { + nxt = NULL; + } + if (next) { + *next = nxt; + } + return str; +} + +// TODO: move this into fmt? +RZ_API ut64 *sdb_fmt_array_num(const char *list) { + ut64 *retp, *ret = NULL; + ut32 size; + const char *next, *ptr = list; + if (list && *list) { + ut32 len = (ut32)sdb_alen(list); + size = sizeof(ut64) * (len + 1); + if (size < len) { + return NULL; + } + retp = ret = (ut64 *)malloc(size); + if (!ret) { + return NULL; + } + *retp++ = len; + do { + const char *str = sdb_anext2(ptr, &next); + ut64 n = sdb_atoi(str); + *retp++ = n; + ptr = next; + } while (next); + } + return ret; +} + +RZ_API char **sdb_fmt_array(const char *list) { + char *_s, **retp, **ret = NULL; + const char *next, *ptr = list; + if (list && *list) { + int len = sdb_alen(list); + retp = ret = (char **)malloc(2 * strlen(list) + + ((len + 1) * sizeof(char *)) + 1); + _s = (char *)ret + ((len + 1) * sizeof(char *)); + if (!ret) { + return NULL; + } + do { + const char *str = sdb_anext2(ptr, &next); + int slen = next ? (next - str) - 1 : (int)strlen(str) + 1; + memcpy(_s, str, slen); + _s[slen] = 0; + *retp++ = _s; + _s += slen + 1; + ptr = next; + } while (next); + *retp = NULL; + } + return ret; +} diff --git a/librz/util/sdb/src/ht_inc.c b/librz/util/sdb/src/ht_inc.c new file mode 100644 index 00000000000..f6f6da95454 --- /dev/null +++ b/librz/util/sdb/src/ht_inc.c @@ -0,0 +1,369 @@ +// SPDX-FileCopyrightText: 2016-2018 crowell +// SPDX-FileCopyrightText: 2016-2018 pancake +// SPDX-FileCopyrightText: 2016-2018 ret2libc +// SPDX-License-Identifier: BSD-3-Clause + +#define LOAD_FACTOR 1 +#define S_ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +// Sizes of the ht. +static const ut32 ht_primes_sizes[] = { + 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, + 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, + 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, + 5839, 7013, 8419, 10103, 12143, 14591, 17519, 21023, + 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, + 108631, 130363, 156437, 187751, 225307, 270371, 324449, + 389357, 467237, 560689, 672827, 807403, 968897, 1162687, + 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, + 4166287, 4999559, 5999471, 7199369 +}; + +static inline ut32 hashfn(HtName_(Ht) * ht, const KEY_TYPE k) { + return ht->opt.hashfn ? ht->opt.hashfn(k) : KEY_TO_HASH(k); +} + +static inline ut32 bucketfn(HtName_(Ht) * ht, const KEY_TYPE k) { + return hashfn(ht, k) % ht->size; +} + +static inline KEY_TYPE dupkey(HtName_(Ht) * ht, const KEY_TYPE k) { + return ht->opt.dupkey ? ht->opt.dupkey(k) : (KEY_TYPE)k; +} + +static inline VALUE_TYPE dupval(HtName_(Ht) * ht, const VALUE_TYPE v) { + return ht->opt.dupvalue ? ht->opt.dupvalue(v) : (VALUE_TYPE)v; +} + +static inline ut32 calcsize_key(HtName_(Ht) * ht, const KEY_TYPE k) { + return ht->opt.calcsizeK ? ht->opt.calcsizeK(k) : 0; +} + +static inline ut32 calcsize_val(HtName_(Ht) * ht, const VALUE_TYPE v) { + return ht->opt.calcsizeV ? ht->opt.calcsizeV(v) : 0; +} + +static inline void freefn(HtName_(Ht) * ht, HT_(Kv) * kv) { + if (ht->opt.freefn) { + ht->opt.freefn(kv); + } +} + +static inline ut32 next_idx(ut32 idx) { + if (idx != UT32_MAX && idx < S_ARRAY_SIZE(ht_primes_sizes) - 1) { + return idx + 1; + } + return UT32_MAX; +} + +static inline ut32 compute_size(ut32 idx, ut32 sz) { + // when possible, use the precomputed prime numbers which help with + // collisions, otherwise, at least make the number odd with |1 + return idx != UT32_MAX && idx < S_ARRAY_SIZE(ht_primes_sizes) ? ht_primes_sizes[idx] : (sz | 1); +} + +static inline bool is_kv_equal(HtName_(Ht) * ht, const KEY_TYPE key, const ut32 key_len, const HT_(Kv) * kv) { + if (key_len != kv->key_len) { + return false; + } + + bool res = key == kv->key; + if (!res && ht->opt.cmp) { + res = !ht->opt.cmp(key, kv->key); + } + return res; +} + +static inline HT_(Kv) * kv_at(HtName_(Ht) * ht, HT_(Bucket) * bt, ut32 i) { + return (HT_(Kv) *)((char *)bt->arr + i * ht->opt.elem_size); +} + +static inline HT_(Kv) * next_kv(HtName_(Ht) * ht, HT_(Kv) * kv) { + return (HT_(Kv) *)((char *)kv + ht->opt.elem_size); +} + +#define BUCKET_FOREACH(ht, bt, j, kv) \ + if ((bt)->arr) \ + for ((j) = 0, (kv) = (bt)->arr; (j) < (bt)->count; (j)++, (kv) = next_kv(ht, kv)) + +#define BUCKET_FOREACH_SAFE(ht, bt, j, count, kv) \ + if ((bt)->arr) \ + for ((j) = 0, (kv) = (bt)->arr, (count) = (ht)->count; \ + (j) < (bt)->count; \ + (j) = (count) == (ht)->count ? j + 1 : j, (kv) = (count) == (ht)->count ? next_kv(ht, kv) : kv, (count) = (ht)->count) + +// Create a new hashtable and return a pointer to it. +// size - number of buckets in the hashtable +// hashfunction - the function that does the hashing, must not be null. +// comparator - the function to check if values are equal, if NULL, just checks +// == (for storing ints). +// keydup - function to duplicate to key (eg strdup), if NULL just does strup. +// valdup - same as keydup, but for values but if NULL just assign +// pair_free - function for freeing a keyvaluepair - if NULL just does free. +// calcsize - function to calculate the size of a value. if NULL, just stores 0. +static HtName_(Ht) * internal_ht_new(ut32 size, ut32 prime_idx, HT_(Options) * opt) { + HtName_(Ht) *ht = calloc(1, sizeof(*ht)); + if (!ht) { + return NULL; + } + ht->size = size; + ht->count = 0; + ht->prime_idx = prime_idx; + ht->table = calloc(ht->size, sizeof(*ht->table)); + if (!ht->table) { + free(ht); + return NULL; + } + ht->opt = *opt; + // if not provided, assume we are dealing with a regular HtName_(Ht), with + // HT_(Kv) as elements + if (ht->opt.elem_size == 0) { + ht->opt.elem_size = sizeof(HT_(Kv)); + } + return ht; +} + +RZ_API HtName_(Ht) * Ht_(new_opt)(HT_(Options) * opt) { + return internal_ht_new(ht_primes_sizes[0], 0, opt); +} + +RZ_API void Ht_(free)(HtName_(Ht) * ht) { + if (!ht) { + return; + } + + ut32 i; + for (i = 0; i < ht->size; i++) { + HT_(Bucket) *bt = &ht->table[i]; + HT_(Kv) * kv; + ut32 j; + + if (ht->opt.freefn) { + BUCKET_FOREACH(ht, bt, j, kv) { + ht->opt.freefn(kv); + } + } + + free(bt->arr); + } + free(ht->table); + free(ht); +} + +// Increases the size of the hashtable by 2. +static void internal_ht_grow(HtName_(Ht) * ht) { + HtName_(Ht) * ht2; + HtName_(Ht) swap; + ut32 idx = next_idx(ht->prime_idx); + ut32 sz = compute_size(idx, ht->size * 2); + ut32 i; + + ht2 = internal_ht_new(sz, idx, &ht->opt); + if (!ht2) { + // we can't grow the ht anymore. Never mind, we'll be slower, + // but everything can continue to work + return; + } + + for (i = 0; i < ht->size; i++) { + HT_(Bucket) *bt = &ht->table[i]; + HT_(Kv) * kv; + ut32 j; + + BUCKET_FOREACH(ht, bt, j, kv) { + Ht_(insert_kv)(ht2, kv, false); + } + } + // And now swap the internals. + swap = *ht; + *ht = *ht2; + *ht2 = swap; + + ht2->opt.freefn = NULL; + Ht_(free)(ht2); +} + +static void check_growing(HtName_(Ht) * ht) { + if (ht->count >= LOAD_FACTOR * ht->size) { + internal_ht_grow(ht); + } +} + +static HT_(Kv) * reserve_kv(HtName_(Ht) * ht, const KEY_TYPE key, const int key_len, bool update) { + HT_(Bucket) *bt = &ht->table[bucketfn(ht, key)]; + HT_(Kv) * kvtmp; + ut32 j; + + BUCKET_FOREACH(ht, bt, j, kvtmp) { + if (is_kv_equal(ht, key, key_len, kvtmp)) { + if (update) { + freefn(ht, kvtmp); + return kvtmp; + } + return NULL; + } + } + + HT_(Kv) *newkvarr = realloc(bt->arr, (bt->count + 1) * ht->opt.elem_size); + if (!newkvarr) { + return NULL; + } + + bt->arr = newkvarr; + bt->count++; + ht->count++; + return kv_at(ht, bt, bt->count - 1); +} + +RZ_API bool Ht_(insert_kv)(HtName_(Ht) * ht, HT_(Kv) * kv, bool update) { + HT_(Kv) *kv_dst = reserve_kv(ht, kv->key, kv->key_len, update); + if (!kv_dst) { + return false; + } + + memcpy(kv_dst, kv, ht->opt.elem_size); + check_growing(ht); + return true; +} + +static bool insert_update(HtName_(Ht) * ht, const KEY_TYPE key, VALUE_TYPE value, bool update) { + ut32 key_len = calcsize_key(ht, key); + HT_(Kv) *kv_dst = reserve_kv(ht, key, key_len, update); + if (!kv_dst) { + return false; + } + + kv_dst->key = dupkey(ht, key); + kv_dst->key_len = key_len; + kv_dst->value = dupval(ht, value); + kv_dst->value_len = calcsize_val(ht, value); + check_growing(ht); + return true; +} + +// Inserts the key value pair key, value into the hashtable. +// Doesn't allow for "update" of the value. +RZ_API bool Ht_(insert)(HtName_(Ht) * ht, const KEY_TYPE key, VALUE_TYPE value) { + return insert_update(ht, key, value, false); +} + +// Inserts the key value pair key, value into the hashtable. +// Does allow for "update" of the value. +RZ_API bool Ht_(update)(HtName_(Ht) * ht, const KEY_TYPE key, VALUE_TYPE value) { + return insert_update(ht, key, value, true); +} + +// Update the key of an element that has old_key as key and replace it with new_key +RZ_API bool Ht_(update_key)(HtName_(Ht) * ht, const KEY_TYPE old_key, const KEY_TYPE new_key) { + // First look for the value associated with old_key + bool found; + VALUE_TYPE value = Ht_(find)(ht, old_key, &found); + if (!found) { + return false; + } + + // Associate the existing value with new_key + bool inserted = insert_update(ht, new_key, value, false); + if (!inserted) { + return false; + } + + // Remove the old_key kv, paying attention to not double free the value + HT_(Bucket) *bt = &ht->table[bucketfn(ht, old_key)]; + const int old_key_len = calcsize_key(ht, old_key); + HT_(Kv) * kv; + ut32 j; + + BUCKET_FOREACH(ht, bt, j, kv) { + if (is_kv_equal(ht, old_key, old_key_len, kv)) { + if (!ht->opt.dupvalue) { + // do not free the value part if dupvalue is not + // set, because the old value has been + // associated with the new key and it should not + // be freed + kv->value = HT_NULL_VALUE; + kv->value_len = 0; + } + freefn(ht, kv); + + void *src = next_kv(ht, kv); + memmove(kv, src, (bt->count - j - 1) * ht->opt.elem_size); + bt->count--; + ht->count--; + return true; + } + } + + return false; +} + +// Returns the corresponding SdbKv entry from the key. +// If `found` is not NULL, it will be set to true if the entry was found, false +// otherwise. +RZ_API HT_(Kv) * Ht_(find_kv)(HtName_(Ht) * ht, const KEY_TYPE key, bool *found) { + if (found) { + *found = false; + } + if (!ht) { + return NULL; + } + + HT_(Bucket) *bt = &ht->table[bucketfn(ht, key)]; + ut32 key_len = calcsize_key(ht, key); + HT_(Kv) * kv; + ut32 j; + + BUCKET_FOREACH(ht, bt, j, kv) { + if (is_kv_equal(ht, key, key_len, kv)) { + if (found) { + *found = true; + } + return kv; + } + } + return NULL; +} + +// Looks up the corresponding value from the key. +// If `found` is not NULL, it will be set to true if the entry was found, false +// otherwise. +RZ_API VALUE_TYPE Ht_(find)(HtName_(Ht) * ht, const KEY_TYPE key, bool *found) { + HT_(Kv) *res = Ht_(find_kv)(ht, key, found); + return res ? res->value : HT_NULL_VALUE; +} + +// Deletes a entry from the hash table from the key, if the pair exists. +RZ_API bool Ht_(delete)(HtName_(Ht) * ht, const KEY_TYPE key) { + HT_(Bucket) *bt = &ht->table[bucketfn(ht, key)]; + ut32 key_len = calcsize_key(ht, key); + HT_(Kv) * kv; + ut32 j; + + BUCKET_FOREACH(ht, bt, j, kv) { + if (is_kv_equal(ht, key, key_len, kv)) { + freefn(ht, kv); + void *src = next_kv(ht, kv); + memmove(kv, src, (bt->count - j - 1) * ht->opt.elem_size); + bt->count--; + ht->count--; + return true; + } + } + return false; +} + +RZ_API void Ht_(foreach)(HtName_(Ht) * ht, HT_(ForeachCallback) cb, void *user) { + ut32 i; + + for (i = 0; i < ht->size; ++i) { + HT_(Bucket) *bt = &ht->table[i]; + HT_(Kv) * kv; + ut32 j, count; + + BUCKET_FOREACH_SAFE(ht, bt, j, count, kv) { + if (!cb(user, kv->key, kv->value)) { + return; + } + } + } +} diff --git a/librz/util/sdb/src/ht_inc.h b/librz/util/sdb/src/ht_inc.h new file mode 100644 index 00000000000..53d5f4aff01 --- /dev/null +++ b/librz/util/sdb/src/ht_inc.h @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2016-2018 crowell +// SPDX-FileCopyrightText: 2016-2018 pancake +// SPDX-FileCopyrightText: 2016-2018 ret2libc +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef HT_TYPE +#error HT_TYPE should be defined before including this header +#endif + +#undef HtName_ +#undef Ht_ +#undef HT_ +#undef KEY_TYPE +#undef VALUE_TYPE +#undef KEY_TO_HASH +#undef HT_NULL_VALUE + +#if HT_TYPE == 1 +#define HtName_(name) name##PP +#define Ht_(name) ht_pp_##name +#define HT_(name) HtPP##name +#define KEY_TYPE void * +#define VALUE_TYPE void * +#define KEY_TO_HASH(x) ((ut32)(uintptr_t)(x)) +#define HT_NULL_VALUE NULL +#elif HT_TYPE == 2 +#define HtName_(name) name##UP +#define Ht_(name) ht_up_##name +#define HT_(name) HtUP##name +#define KEY_TYPE ut64 +#define VALUE_TYPE void * +#define KEY_TO_HASH(x) ((ut32)(x)) +#define HT_NULL_VALUE 0 +#elif HT_TYPE == 3 +#define HtName_(name) name##UU +#define Ht_(name) ht_uu_##name +#define HT_(name) HtUU##name +#define KEY_TYPE ut64 +#define VALUE_TYPE ut64 +#define KEY_TO_HASH(x) ((ut32)(x)) +#define HT_NULL_VALUE 0 +#else +#define HtName_(name) name##PU +#define Ht_(name) ht_pu_##name +#define HT_(name) HtPU##name +#define KEY_TYPE void * +#define VALUE_TYPE ut64 +#define KEY_TO_HASH(x) ((ut32)(uintptr_t)(x)) +#define HT_NULL_VALUE 0 +#endif + +#include "ls.h" +#include + +/* Kv represents a single key/value element in the hashtable */ +typedef struct Ht_(kv) { + KEY_TYPE key; + VALUE_TYPE value; + ut32 key_len; + ut32 value_len; +} +HT_(Kv); + +typedef void (*HT_(KvFreeFunc))(HT_(Kv) *); +typedef KEY_TYPE (*HT_(DupKey))(const KEY_TYPE); +typedef VALUE_TYPE (*HT_(DupValue))(const VALUE_TYPE); +typedef ut32 (*HT_(CalcSizeK))(const KEY_TYPE); +typedef ut32 (*HT_(CalcSizeV))(const VALUE_TYPE); +typedef ut32 (*HT_(HashFunction))(const KEY_TYPE); +typedef int (*HT_(ListComparator))(const KEY_TYPE, const KEY_TYPE); +typedef bool (*HT_(ForeachCallback))(void *user, const KEY_TYPE, const VALUE_TYPE); + +typedef struct Ht_(bucket_t) { + HT_(Kv) * arr; + ut32 count; +} +HT_(Bucket); + +/* Options contain all the settings of the hashtable */ +typedef struct Ht_(options_t) { + HT_(ListComparator) + cmp; // Function for comparing values. Returns 0 if eq. + HT_(HashFunction) + hashfn; // Function for hashing items in the hash table. + HT_(DupKey) + dupkey; // Function for making a copy of key + HT_(DupValue) + dupvalue; // Function for making a copy of value + HT_(CalcSizeK) + calcsizeK; // Function to determine the key's size + HT_(CalcSizeV) + calcsizeV; // Function to determine the value's size + HT_(KvFreeFunc) + freefn; // Function to free the keyvalue store + size_t elem_size; // Size of each HtKv element (useful for subclassing like SdbKv) +} +HT_(Options); + +/* Ht is the hashtable structure */ +typedef struct Ht_(t) { + ut32 size; // size of the hash table in buckets. + ut32 count; // number of stored elements. + HT_(Bucket) * table; // Actual table. + ut32 prime_idx; + HT_(Options) + opt; +} +HtName_(Ht); + +// Create a new Ht with the provided Options +RZ_API HtName_(Ht) * Ht_(new_opt)(HT_(Options) * opt); +// Destroy a hashtable and all of its entries. +RZ_API void Ht_(free)(HtName_(Ht) * ht); +// Insert a new Key-Value pair into the hashtable. If the key already exists, returns false. +RZ_API bool Ht_(insert)(HtName_(Ht) * ht, const KEY_TYPE key, VALUE_TYPE value); +// Insert a new Key-Value pair into the hashtable, or updates the value if the key already exists. +RZ_API bool Ht_(update)(HtName_(Ht) * ht, const KEY_TYPE key, VALUE_TYPE value); +// Update the key of an element in the hashtable +RZ_API bool Ht_(update_key)(HtName_(Ht) * ht, const KEY_TYPE old_key, const KEY_TYPE new_key); +// Delete a key from the hashtable. +RZ_API bool Ht_(delete)(HtName_(Ht) * ht, const KEY_TYPE key); +// Find the value corresponding to the matching key. +RZ_API VALUE_TYPE Ht_(find)(HtName_(Ht) * ht, const KEY_TYPE key, bool *found); +// Iterates over all elements in the hashtable, calling the cb function on each Kv. +// If the cb returns false, the iteration is stopped. +// cb should not modify the hashtable. +// NOTE: cb can delete the current element, but it should be avoided +RZ_API void Ht_(foreach)(HtName_(Ht) * ht, HT_(ForeachCallback) cb, void *user); + +RZ_API HT_(Kv) * Ht_(find_kv)(HtName_(Ht) * ht, const KEY_TYPE key, bool *found); +RZ_API bool Ht_(insert_kv)(HtName_(Ht) * ht, HT_(Kv) * kv, bool update); diff --git a/librz/util/sdb/src/ht_pp.c b/librz/util/sdb/src/ht_pp.c new file mode 100644 index 00000000000..da6a81feee7 --- /dev/null +++ b/librz/util/sdb/src/ht_pp.c @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2016-2018 crowell +// SPDX-FileCopyrightText: 2016-2018 pancake +// SPDX-FileCopyrightText: 2016-2018 ret2libc +// SPDX-License-Identifier: BSD-3-Clause + +#include "sdb.h" +#include "ht_pp.h" +#include "ht_inc.c" + +static HtName_(Ht) * internal_ht_default_new(ut32 size, ut32 prime_idx, HT_(DupValue) valdup, HT_(KvFreeFunc) pair_free, HT_(CalcSizeV) calcsizeV) { + HT_(Options) + opt = { + .cmp = (HT_(ListComparator))strcmp, + .hashfn = (HT_(HashFunction))sdb_hash, + .dupkey = (HT_(DupKey))strdup, + .dupvalue = valdup, + .calcsizeK = (HT_(CalcSizeK))strlen, + .calcsizeV = calcsizeV, + .freefn = pair_free, + .elem_size = sizeof(HT_(Kv)), + }; + return internal_ht_new(size, prime_idx, &opt); +} + +// creates a default HtPP that has strings as keys +RZ_API HtName_(Ht) * Ht_(new)(HT_(DupValue) valdup, HT_(KvFreeFunc) pair_free, HT_(CalcSizeV) calcsizeV) { + return internal_ht_default_new(ht_primes_sizes[0], 0, valdup, pair_free, calcsizeV); +} + +static void free_kv_key(HT_(Kv) * kv) { + free(kv->key); +} + +// creates a default HtPP that has strings as keys but does not dup, nor free the values +RZ_API HtName_(Ht) * Ht_(new0)(void) { + return Ht_(new)(NULL, free_kv_key, NULL); +} + +RZ_API HtName_(Ht) * Ht_(new_size)(ut32 initial_size, HT_(DupValue) valdup, HT_(KvFreeFunc) pair_free, HT_(CalcSizeV) calcsizeV) { + ut32 i = 0; + + while (i < S_ARRAY_SIZE(ht_primes_sizes) && + ht_primes_sizes[i] * LOAD_FACTOR < initial_size) { + i++; + } + if (i == S_ARRAY_SIZE(ht_primes_sizes)) { + i = UT32_MAX; + } + + ut32 sz = compute_size(i, (ut32)(initial_size * (2 - LOAD_FACTOR))); + return internal_ht_default_new(sz, i, valdup, pair_free, calcsizeV); +} diff --git a/librz/util/sdb/src/ht_pp.h b/librz/util/sdb/src/ht_pp.h new file mode 100644 index 00000000000..57a66dd9aad --- /dev/null +++ b/librz/util/sdb/src/ht_pp.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2016-2018 crowell +// SPDX-FileCopyrightText: 2016-2018 pancake +// SPDX-FileCopyrightText: 2016-2018 ret2libc +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef SDB_HT_PP_H +#define SDB_HT_PP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This header provides an hashtable HtPP that has void* as key and void* as + * value. The API functions starts with "ht_pp_" and the types starts with "HtPP". + */ +#define HT_TYPE 1 +#include "ht_inc.h" + +RZ_API HtName_(Ht) * Ht_(new0)(void); +RZ_API HtName_(Ht) * Ht_(new)(HT_(DupValue) valdup, HT_(KvFreeFunc) pair_free, HT_(CalcSizeV) valueSize); +RZ_API HtName_(Ht) * Ht_(new_size)(ut32 initial_size, HT_(DupValue) valdup, HT_(KvFreeFunc) pair_free, HT_(CalcSizeV) valueSize); +#undef HT_TYPE + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librz/util/sdb/src/ht_pu.c b/librz/util/sdb/src/ht_pu.c new file mode 100644 index 00000000000..d90d583060d --- /dev/null +++ b/librz/util/sdb/src/ht_pu.c @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2016-2018 crowell +// SPDX-FileCopyrightText: 2016-2018 pancake +// SPDX-FileCopyrightText: 2016-2018 ret2libc +// SPDX-License-Identifier: BSD-3-Clause + +#include "sdb.h" +#include "ht_pu.h" +#include "ht_inc.c" + +static void free_kv_key(HT_(Kv) * kv) { + free(kv->key); +} + +// creates a default HtPU that has strings as keys +RZ_API HtName_(Ht) * Ht_(new0)(void) { + HT_(Options) + opt = { + .cmp = (HT_(ListComparator))strcmp, + .hashfn = (HT_(HashFunction))sdb_hash, + .dupkey = (HT_(DupKey))strdup, + .dupvalue = NULL, + .calcsizeK = (HT_(CalcSizeK))strlen, + .calcsizeV = NULL, + .freefn = free_kv_key, + .elem_size = sizeof(HT_(Kv)), + }; + return Ht_(new_opt)(&opt); +} diff --git a/librz/util/sdb/src/ht_pu.h b/librz/util/sdb/src/ht_pu.h new file mode 100644 index 00000000000..a5812613cc5 --- /dev/null +++ b/librz/util/sdb/src/ht_pu.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2016-2018 crowell +// SPDX-FileCopyrightText: 2016-2018 pancake +// SPDX-FileCopyrightText: 2016-2018 ret2libc +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef SDB_HT_PU_H +#define SDB_HT_PU_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This header provides an hashtable HtPU that has void* as key and ut64 as + * value. The API functions starts with "ht_pu_" and the types starts with "HtPU". + */ +#define HT_TYPE 4 +#include "ht_inc.h" + +RZ_API HtName_(Ht) * Ht_(new0)(void); +#undef HT_TYPE + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librz/util/sdb/src/ht_up.c b/librz/util/sdb/src/ht_up.c new file mode 100644 index 00000000000..c86ee90cd33 --- /dev/null +++ b/librz/util/sdb/src/ht_up.c @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2016-2018 crowell +// SPDX-FileCopyrightText: 2016-2018 pancake +// SPDX-FileCopyrightText: 2016-2018 ret2libc +// SPDX-License-Identifier: BSD-3-Clause + +#include "ht_up.h" +#include "ht_inc.c" + +static HtName_(Ht) * internal_ht_default_new(ut32 size, ut32 prime_idx, HT_(DupValue) valdup, HT_(KvFreeFunc) pair_free, HT_(CalcSizeV) calcsizeV) { + HT_(Options) + opt = { + .cmp = NULL, + .hashfn = NULL, // TODO: use a better hash function for numbers + .dupkey = NULL, + .dupvalue = valdup, + .calcsizeK = NULL, + .calcsizeV = calcsizeV, + .freefn = pair_free, + .elem_size = sizeof(HT_(Kv)), + }; + return internal_ht_new(size, prime_idx, &opt); +} + +RZ_API HtName_(Ht) * Ht_(new)(HT_(DupValue) valdup, HT_(KvFreeFunc) pair_free, HT_(CalcSizeV) calcsizeV) { + return internal_ht_default_new(ht_primes_sizes[0], 0, valdup, pair_free, calcsizeV); +} + +// creates a default HtUP that does not dup, nor free the values +RZ_API HtName_(Ht) * Ht_(new0)(void) { + return Ht_(new)(NULL, NULL, NULL); +} + +RZ_API HtName_(Ht) * Ht_(new_size)(ut32 initial_size, HT_(DupValue) valdup, HT_(KvFreeFunc) pair_free, HT_(CalcSizeV) calcsizeV) { + ut32 i = 0; + + while (i < S_ARRAY_SIZE(ht_primes_sizes) && + ht_primes_sizes[i] * LOAD_FACTOR < initial_size) { + i++; + } + if (i == S_ARRAY_SIZE(ht_primes_sizes)) { + i = UT32_MAX; + } + + ut32 sz = compute_size(i, (ut32)(initial_size * (2 - LOAD_FACTOR))); + return internal_ht_default_new(sz, i, valdup, pair_free, calcsizeV); +} diff --git a/librz/util/sdb/src/ht_up.h b/librz/util/sdb/src/ht_up.h new file mode 100644 index 00000000000..48c39546869 --- /dev/null +++ b/librz/util/sdb/src/ht_up.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2016-2018 crowell +// SPDX-FileCopyrightText: 2016-2018 pancake +// SPDX-FileCopyrightText: 2016-2018 ret2libc +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef SDB_HT_UP_H +#define SDB_HT_UP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This header provides an hashtable HtUP that has ut64 as key and void* as + * value. The API functions starts with "ht_up_" and the types starts with "HtUP". + */ +#define HT_TYPE 2 +#include "ht_inc.h" + +RZ_API HtName_(Ht) * Ht_(new0)(void); +RZ_API HtName_(Ht) * Ht_(new)(HT_(DupValue) valdup, HT_(KvFreeFunc) pair_free, HT_(CalcSizeV) valueSize); +RZ_API HtName_(Ht) * Ht_(new_size)(ut32 initial_size, HT_(DupValue) valdup, HT_(KvFreeFunc) pair_free, HT_(CalcSizeV) valueSize); +#undef HT_TYPE + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librz/util/sdb/src/ht_uu.c b/librz/util/sdb/src/ht_uu.c new file mode 100644 index 00000000000..c3d04baf9f4 --- /dev/null +++ b/librz/util/sdb/src/ht_uu.c @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2016-2018 crowell +// SPDX-FileCopyrightText: 2016-2018 pancake +// SPDX-FileCopyrightText: 2016-2018 ret2libc +// SPDX-License-Identifier: BSD-3-Clause + +#include "ht_uu.h" + +#include "ht_inc.c" + +RZ_API HtName_(Ht) * Ht_(new0)(void) { + HT_(Options) + opt = { + .cmp = NULL, + .hashfn = NULL, + .dupkey = NULL, + .dupvalue = NULL, + .calcsizeK = NULL, + .calcsizeV = NULL, + .freefn = NULL + }; + return Ht_(new_opt)(&opt); +} diff --git a/librz/util/sdb/src/ht_uu.h b/librz/util/sdb/src/ht_uu.h new file mode 100644 index 00000000000..c139c2547c3 --- /dev/null +++ b/librz/util/sdb/src/ht_uu.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2016-2018 crowell +// SPDX-FileCopyrightText: 2016-2018 pancake +// SPDX-FileCopyrightText: 2016-2018 ret2libc +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef SDB_HT_H_ +#define SDB_HT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This header provides an hashtable Ht that has ut64 as key and ut64 as + * value. The API functions starts with "ht_" and the types starts with "Ht". + */ +#define HT_TYPE 3 +#include "ht_inc.h" + +RZ_API HtName_(Ht) * Ht_(new0)(void); +#undef HT_TYPE + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librz/util/sdb/src/journal.c b/librz/util/sdb/src/journal.c new file mode 100644 index 00000000000..60b126f83de --- /dev/null +++ b/librz/util/sdb/src/journal.c @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2011-2016 pancake +// SPDX-License-Identifier: MIT + +#include "sdb.h" +#include + +static const char *sdb_journal_filename(Sdb *s) { + return (s && s->name) + ? sdb_fmt("%s.journal", s->name) + : NULL; +} + +RZ_API bool sdb_journal_close(Sdb *s) { + if (s->journal == -1) { + return false; + } + close(s->journal); + s->journal = -1; + unlink(sdb_journal_filename(s)); + return true; +} + +RZ_API bool sdb_journal_open(Sdb *s) { + const char *filename; + if (!s || !s->name) { + return false; + } + filename = sdb_journal_filename(s); + if (!filename) { + return false; + } + close(s->journal); + s->journal = open(filename, O_CREAT | O_RDWR | O_APPEND, 0600); + return s->journal != -1; +} + +// TODO boolify and save changes somewhere else? or just dont count that? +RZ_API int sdb_journal_load(Sdb *s) { + int rr, sz, fd, changes = 0; + char *eq, *str, *cur, *ptr = NULL; + if (!s) { + return 0; + } + fd = s->journal; + if (fd == -1) { + return 0; + } + sz = lseek(fd, 0, SEEK_END); + if (sz < 1) { + return 0; + } + lseek(fd, 0, SEEK_SET); + str = malloc(sz + 1); + if (!str) { + return 0; + } + rr = read(fd, str, sz); + if (rr < 0) { + free(str); + return 0; + } + str[sz] = 0; + for (cur = str;;) { + ptr = strchr(cur, '\n'); + if (!ptr) { + break; + } + *ptr = 0; + eq = strchr(cur, '='); + if (eq) { + *eq++ = 0; + sdb_set(s, cur, eq, 0); + changes++; + } + cur = ptr + 1; + } + free(str); + return changes; +} + +RZ_API bool sdb_journal_log(Sdb *s, const char *key, const char *val) { + if (s->journal == -1) { + return false; + } + const char *str = sdb_fmt("%s=%s\n", key, val); + int len = strlen(str); + if (write(s->journal, str, len) != len) { + return false; + } +#if HAVE_HEADER_SYS_MMAN_H + (void)fsync(s->journal); +#endif + return true; +} + +RZ_API bool sdb_journal_clear(Sdb *s) { + if (s->journal != -1) { + return !ftruncate(s->journal, 0); + } + return false; +} + +RZ_API bool sdb_journal_unlink(Sdb *s) { + const char *filename = sdb_journal_filename(s); + sdb_journal_close(s); + if (filename) { + return !unlink(filename); + } + return false; +} diff --git a/librz/util/sdb/src/json.c b/librz/util/sdb/src/json.c new file mode 100644 index 00000000000..f3efd0c563b --- /dev/null +++ b/librz/util/sdb/src/json.c @@ -0,0 +1,356 @@ +// SPDX-FileCopyrightText: 2012-2016 pancake +// SPDX-License-Identifier: MIT + +#include +#include "sdb.h" +#include "json/rangstr.c" +#include "json/js0n.c" +#include "json/path.c" +#include "json/api.c" +#include "json/indent.c" + +RZ_API char *sdb_json_get_str(const char *json, const char *path) { + Rangstr rs = json_get(json, path); + return rangstr_dup(&rs); +} + +RZ_API bool sdb_json_get_bool(const char *json, const char *path) { + Rangstr rs = json_get(json, path); + const char *p = rs.p + rs.f; + return (rangstr_length(&rs) == 4 && !strncmp(p, "true", 4)); +} + +RZ_API char *sdb_json_get(Sdb *s, const char *k, const char *p, ut32 *cas) { + Rangstr rs; + char *u, *v = sdb_get(s, k, cas); + if (!v) { + return NULL; + } + rs = json_get(v, p); + u = rangstr_dup(&rs); + free(v); + return u; +} + +RZ_API int sdb_json_num_inc(Sdb *s, const char *k, const char *p, int n, ut32 cas) { + ut32 c; + int cur = sdb_json_num_get(s, k, p, &c); + if (cas && c != cas) { + return 0; + } + sdb_json_num_set(s, k, p, cur + n, cas); + return cur + n; +} + +RZ_API int sdb_json_num_dec(Sdb *s, const char *k, const char *p, int n, ut32 cas) { + ut32 c; + int cur = sdb_json_num_get(s, k, p, &c); + if (cas && c != cas) { + return 0; + } + sdb_json_num_set(s, k, p, cur - n, cas); + return cur - n; +} + +RZ_API int sdb_json_num_get(Sdb *s, const char *k, const char *p, ut32 *cas) { + char *v = sdb_get(s, k, cas); + if (v) { + Rangstr rs = json_get(v, p); + int ret = rangstr_int(&rs); + free(v); + return ret; + } + return 0; +} + +static int findkey(Rangstr *rs) { + int i; + for (i = rs->f; i > 0; i--) { + // Find the quote after the key + if (rs->p[i] == '"') { + for (--i; i > 0; i--) { + // Find the quote before the key + if (rs->p[i] == '"') { + return i; + } + } + } + } + return -1; +} + +static bool isstring(const char *s) { + if (!strcmp(s, "true")) { + return false; + } + if (!strcmp(s, "false")) { + return false; + } + for (; *s; s++) { + if (*s < '0' || *s > '9') { + return true; + } + } + return false; +} + +// JSON only supports base16 numbers +RZ_API int sdb_json_num_set(Sdb *s, const char *k, const char *p, int v, ut32 cas) { + char *_str, str[64]; + _str = sdb_itoa(v, str, 10); + return sdb_json_set(s, k, p, _str, cas); +} + +RZ_API int sdb_json_unset(Sdb *s, const char *k, const char *p, ut32 cas) { + return sdb_json_set(s, k, p, NULL, cas); +} + +RZ_API bool sdb_json_set(Sdb *s, const char *k, const char *p, const char *v, ut32 cas) { + int l, idx, len[3], jslen = 0; + char *b, *str = NULL; + const char *beg[3]; + const char *end[3]; + const char *js; + Rangstr rs; + ut32 c; + + if (!s || !k || !v) { + return false; + } + js = sdb_const_get_len(s, k, &jslen, &c); + if (!js) { + const int v_len = strlen(v); + const int p_len = strlen(p); + b = malloc(p_len + v_len + 8); + if (b) { + int is_str = isstring(v); + const char *q = is_str ? "\"" : ""; + sprintf(b, "{\"%s\":%s%s%s}", p, q, v, q); +#if 0 + /* disabled because it memleaks */ + sdb_set_owned (s, k, b, cas); +#else + sdb_set(s, k, b, cas); + free(b); +#endif + return true; + } + return false; + } + jslen++; + if (cas && c != cas) { + return false; + } + rs = json_get(js, p); + if (!rs.p) { + // jslen already comprehends the NULL-terminator and is + // ensured to be positive by sdb_const_get_len + // 7 corresponds to the length of '{"":"",' + size_t buf_len = jslen + strlen(p) + strlen(v) + 7; + char *buf = malloc(buf_len); + if (buf) { + int curlen, is_str = isstring(v); + const char *quote = is_str ? "\"" : ""; + const char *end = ""; // XX: or comma + if (js[0] && js[1] != '}') { + end = ","; + } + curlen = sprintf(buf, "{\"%s\":%s%s%s%s", + p, quote, v, quote, end); + strcpy(buf + curlen, js + 1); + // transfer ownership + sdb_set_owned(s, k, buf, cas); + return true; + } + // invalid json? + return false; + } + + // rs.p and js point to the same memory location + beg[0] = js; + end[0] = rs.p + rs.f; + len[0] = WLEN(0); + + if (*v) { + beg[1] = v; + end[1] = v + strlen(v); + len[1] = WLEN(1); + } + + beg[2] = rs.p + rs.t; + end[2] = js + jslen; + len[2] = WLEN(2); + + // TODO: accelerate with small buffer in stack for small jsons + if (*v) { + int is_str = isstring(v); + // 2 is the maximum amount of quotes that can be inserted + int msz = len[0] + len[1] + len[2] + strlen(v) + 2; + if (msz < 1) { + return false; + } + str = malloc(msz); + if (!str) { + return false; + } + idx = len[0]; + memcpy(str, beg[0], idx); + if (is_str) { + if (beg[2][0] != '"') { + str[idx] = '"'; + idx++; + } + } else { + if (beg[2][0] == '"') { + beg[2]++; + len[2]--; + } + } + l = len[1]; + memcpy(str + idx, beg[1], l); + idx += len[1]; + if (is_str) { + // TODO: add quotes + if (beg[2][0] != '"') { + str[idx] = '"'; + idx++; + } + } else { + if (beg[2][0] == '"') { + beg[2]++; + len[2]--; + } + } + l = len[2]; + memcpy(str + idx, beg[2], l); + str[idx + l] = 0; + } else { + int kidx; + // DELETE KEY + rs.f -= 2; + kidx = findkey(&rs); + len[0] = RZ_MAX(1, kidx - 1); + + // Delete quote if deleted value was a string + if (beg[2][0] == '"') { + beg[2]++; + len[2]--; + } + + // If not the last key, delete comma + if (len[2] != 2) { + beg[2]++; + len[2]--; + } + + str = malloc(len[0] + len[2] + 1); + if (!str) { + return false; + } + + memcpy(str, beg[0], len[0]); + memcpy(str + len[0], beg[2], len[2]); + str[len[0] + len[2]] = 0; + } + sdb_set_owned(s, k, str, cas); + return true; +} + +RZ_API const char *sdb_json_format(SdbJsonString *s, const char *fmt, ...) { + char *arg_s, *x, tmp[128]; + ut64 arg_l; + int i, arg_i; + double arg_f; + va_list ap; +#define JSONSTR_ALLOCATE(y) \ + if (s->len + y > s->blen) { \ + s->blen *= 2; \ + x = realloc(s->buf, s->blen); \ + if (!x) { \ + va_end(ap); \ + return NULL; \ + } \ + s->buf = x; \ + } + if (!s) { + return NULL; + } + if (!s->buf) { + s->blen = 1024; + s->buf = malloc(s->blen); + if (!s->buf) { + return NULL; + } + *s->buf = 0; + } + if (!fmt || !*fmt) { + return s->buf; + } + va_start(ap, fmt); + for (; *fmt; fmt++) { + if (*fmt == '%') { + fmt++; + switch (*fmt) { + case 'b': + JSONSTR_ALLOCATE(32); + arg_i = va_arg(ap, int); + arg_i = arg_i ? 4 : 5; + memcpy(s->buf + s->len, (arg_i == 4) ? "true" : "false", 5); + s->len += arg_i; + break; + case 'f': + JSONSTR_ALLOCATE(32); + arg_f = va_arg(ap, double); + snprintf(tmp, sizeof(tmp), "%f", arg_f); + memcpy(s->buf + s->len, tmp, strlen(tmp)); + s->len += strlen(tmp); + break; + case 'l': + JSONSTR_ALLOCATE(32); + arg_l = va_arg(ap, ut64); + snprintf(tmp, sizeof(tmp), "0x%" PFMT64x "x", arg_l); + memcpy(s->buf + s->len, tmp, strlen(tmp)); + s->len += strlen(tmp); + break; + case 'd': + case 'i': + JSONSTR_ALLOCATE(32); + arg_i = va_arg(ap, int); + snprintf(tmp, sizeof(tmp), "%d", arg_i); + memcpy(s->buf + s->len, tmp, strlen(tmp)); + s->len += strlen(tmp); + break; + case 's': + arg_s = va_arg(ap, char *); + JSONSTR_ALLOCATE(strlen(arg_s) + 3); + s->buf[s->len++] = '"'; + for (i = 0; arg_s[i]; i++) { + if (arg_s[i] == '"') { + s->buf[s->len++] = '\\'; + } + s->buf[s->len++] = arg_s[i]; + } + s->buf[s->len++] = '"'; + break; + } + } else { + JSONSTR_ALLOCATE(10); + s->buf[s->len++] = *fmt; + } + s->buf[s->len] = 0; + } + va_end(ap); + return s->buf; +} + +#if 0 +int main () { + SdbJsonString s = { + 0 + }; + sdb_json_format (&s, "[{%s:%d},%b]", "Hello \"world\"", 1024, 3); + printf ("%s\n", sdb_json_format (&s, 0)); + sdb_json_format_free (&s); + return 0; +} +#endif diff --git a/librz/util/sdb/src/json/README b/librz/util/sdb/src/json/README new file mode 100644 index 00000000000..467f49db83e --- /dev/null +++ b/librz/util/sdb/src/json/README @@ -0,0 +1,20 @@ +JSON support for sdb +==================== + +Use js0n: https://github.com/quartzjer/js0n + +git clone git://github.com/quartzjer/js0n.git + +---------- + +user.pancake={"name":"Pancake","pass":"4e2e631e3671727c3f7d8d11fc4dcb0d"} +user.blah={"name":"Bin Daboo","pass":"bd20ae2b25e8d4ff64e0720a5b4162d2"} +user.boun={"name":"Bob Union","pass":"2f03be08fcddd9a889e8c9d8de933cea"} + +user.pancake?name +user.pancake?pass + +user.pancake?name=My name + +user?name=Bob Union => user.boun +user?name~B => user.boun diff --git a/librz/util/sdb/src/json/api.c b/librz/util/sdb/src/json/api.c new file mode 100644 index 00000000000..c12aaaa1b5c --- /dev/null +++ b/librz/util/sdb/src/json/api.c @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2012 pancake +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include "rangstr.h" + +/* public sdb api */ +char *api_json_get(const char *s, const char *p) { + Rangstr rs = json_get(s, p); + return rangstr_dup(&rs); +} + +char *api_json_set(const char *s, const char *k, const char *v) { + const char *beg[3]; + const char *end[3]; + int idx, len[3]; + char *str = NULL; + Rangstr rs = json_get(s, k); + if (!rs.p) { + return NULL; + } +#define WLEN(x) (int)(size_t)(end[x] - beg[x]) + + beg[0] = s; + end[0] = rs.p + rs.f; + len[0] = WLEN(0); + + beg[1] = v; + end[1] = v + strlen(v); + len[1] = WLEN(1); + + beg[2] = rs.p + rs.t; + end[2] = s + strlen(s); + len[2] = WLEN(2); + + str = malloc(len[0] + len[1] + len[2] + 1); + if (!str) { + return NULL; + } + idx = len[0]; + memcpy(str, beg[0], idx); + memcpy(str + idx, beg[1], len[1]); + idx += len[1]; + memcpy(str + idx, beg[2], len[2]); + str[idx + len[2]] = 0; + return str; +} + +char *api_json_seti(const char *s, const char *k, int a) { + char str[64]; + sprintf(str, "%d", a); + return api_json_set(s, k, str); +} diff --git a/librz/util/sdb/src/json/indent.c b/librz/util/sdb/src/json/indent.c new file mode 100644 index 00000000000..ea3b96411af --- /dev/null +++ b/librz/util/sdb/src/json/indent.c @@ -0,0 +1,140 @@ +// SPDX-FileCopyrightText: 2012-2018 pancake +// SPDX-License-Identifier: MIT + +#include + +static void doIndent(int idt, char **o, const char *tab) { + int i; + char *x; + for (i = 0; i < idt; i++) { + for (x = (char *)tab; *x; x++) { + *(*o)++ = *x; + } + } +} + +RZ_API char *sdb_json_indent(const char *s, const char *tab) { + int idx, indent = 0; + int instr = 0; + size_t o_size = 0; + char *o, *O; + if (!s) { + return NULL; + } + + size_t tab_len = strlen(tab); + for (idx = 0; s[idx]; idx++) { + if (o_size > INT_MAX - (indent * tab_len + 2)) { + return NULL; + } + + if (s[idx] == '{' || s[idx] == '[') { + indent++; + // 2 corresponds to the \n and the parenthesis + o_size += indent * tab_len + 2; + } else if (s[idx] == '}' || s[idx] == ']') { + if (indent > 0) { + indent--; + } + // 2 corresponds to the \n and the parenthesis + o_size += indent * tab_len + 2; + } else if (s[idx] == ',') { + // 2 corresponds to the \n and the , + o_size += indent * tab_len + 2; + } else if (s[idx] == ':') { + o_size += 2; + } else { + o_size++; + } + } + // 2 corresponds to the last \n and \0 + o_size += 2; + indent = 0; + + O = malloc(o_size + 1); + if (!O) { + return NULL; + } + + for (o = O; *s; s++) { + if (instr) { + if (s[0] == '"') { + instr = 0; + } else if (s[0] == '\\' && s[1] == '"') { + *o++ = *s; + } + *o++ = *s; + continue; + } else { + if (s[0] == '"') { + instr = 1; + } + } + if (*s == '\n' || *s == '\r' || *s == '\t' || *s == ' ') { + continue; + } + switch (*s) { + case ':': + *o++ = *s; + *o++ = ' '; + break; + case ',': + *o++ = *s; + *o++ = '\n'; + doIndent(indent, &o, tab); + break; + case '{': + case '[': + *o++ = *s; + *o++ = (indent != -1) ? '\n' : ' '; + indent++; + doIndent(indent, &o, tab); + break; + case '}': + case ']': + *o++ = '\n'; + indent--; + doIndent(indent, &o, tab); + *o++ = *s; + break; + default: + *o++ = *s; + } + } + *o++ = '\n'; + *o = 0; + + return O; +} + +// TODO: move to utils? +RZ_API char *sdb_json_unindent(const char *s) { + int instr = 0; + int len = strlen(s); + char *o, *O = malloc(len + 1); + if (!O) { + return NULL; + } + memset(O, 0, len); + for (o = O; *s; s++) { + if (instr) { + if (s[0] != '"') { + if (s[0] == '\\' && s[1] == '"') { + *o++ = *s; + } + } else { + instr = 0; + } + *o++ = *s; + continue; + } else if (s[0] == '"') { + instr = 1; + } + if (*s == '\n' || *s == '\r' || *s == '\t' || *s == ' ') { + continue; + } + *o++ = *s; + } + *o = 0; + return O; +} diff --git a/librz/util/sdb/src/json/js0n.c b/librz/util/sdb/src/json/js0n.c new file mode 100644 index 00000000000..410f8144b42 --- /dev/null +++ b/librz/util/sdb/src/json/js0n.c @@ -0,0 +1,306 @@ +// SPDX-FileCopyrightText: 2010-2018 Jeremie Miller +// SPDX-License-Identifier: MIT + +// opportunity to further optimize would be having different jump tables for higher depths + +#include "rangstr.h" + +#define PUSH(i) \ + if (depth == 1) \ + prev = *out++ = ((cur + i) - js) +#define CAP(i) \ + if (depth == 1) \ + prev = *out++ = ((cur + i) - (js + prev) + 1) + +#ifdef _MSC_VER +#define HAVE_COMPUTED_GOTOS 0 +#elif __EMSCRIPTEN__ +#define HAVE_COMPUTED_GOTOS 0 +#else +#define HAVE_COMPUTED_GOTOS 1 +#endif + +#if HAVE_COMPUTED_GOTOS + +#if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +#pragma GCC diagnostic push +#endif +#pragma GCC diagnostic ignored "-Wunknown-pragmas" +#pragma GCC diagnostic ignored "-Wpragmas" +#pragma GCC diagnostic ignored "-Winitializer-overrides" + +#define HAVE_RAWSTR 0 + +int sdb_js0n(const ut8 *js, RangstrType len, RangstrType *out) { + ut32 prev = 0; + const ut8 *cur, *end; + int depth = 0, utf8_remain = 0; + static void *gostruct[] = { + [0 ... 255] = &&l_bad, + ['\t'] = &&l_loop, + [' '] = &&l_loop, + ['\r'] = &&l_loop, + ['\n'] = &&l_loop, + ['"'] = &&l_qup, + [':'] = &&l_loop, + [','] = &&l_loop, + ['['] = &&l_up, + [']'] = &&l_down, // tracking [] and {} individually would allow fuller validation but is really messy + ['{'] = &&l_up, + ['}'] = &&l_down, +// TODO: add support for rawstrings +#if HAVE_RAWSTR + ['a' ... 'z'] = &&l_rawstr, +#else + ['-'] = &&l_bare, + [48 ... 57] = &&l_bare, // 0-9 + ['t'] = &&l_bare, + ['f'] = &&l_bare, + ['n'] = &&l_bare // true, false, null +#endif + }; + static void *gobare[] = { + [0 ... 31] = &&l_bad, + [32 ... 126] = &&l_loop, // could be more pedantic/validation-checking + ['\t'] = &&l_unbare, + [' '] = &&l_unbare, + ['\r'] = &&l_unbare, + ['\n'] = &&l_unbare, + [','] = &&l_unbare, + [']'] = &&l_unbare, + ['}'] = &&l_unbare, + [127 ... 255] = &&l_bad + }; +#if HAVE_RAWSTR + static void *gorawstr[] = { + [0 ... 31] = &&l_bad, [127] = &&l_bad, [32 ... 126] = &&l_loop, ['\\'] = &&l_esc, [':'] = &&l_qdown, [128 ... 191] = &&l_bad, [192 ... 223] = &&l_utf8_2, [224 ... 239] = &&l_utf8_3, [240 ... 247] = &&l_utf8_4, [248 ... 255] = &&l_bad + }; +#endif + static void *gostring[] = { + [0 ... 31] = &&l_bad, [127] = &&l_bad, [32 ... 126] = &&l_loop, ['\\'] = &&l_esc, ['"'] = &&l_qdown, [128 ... 191] = &&l_bad, [192 ... 223] = &&l_utf8_2, [224 ... 239] = &&l_utf8_3, [240 ... 247] = &&l_utf8_4, [248 ... 255] = &&l_bad + }; + static void *goutf8_continue[] = { + [0 ... 127] = &&l_bad, + [128 ... 191] = &&l_utf_continue, + [192 ... 255] = &&l_bad + }; + static void *goesc[] = { + [0 ... 255] = &&l_bad, + ['"'] = &&l_unesc, + ['\\'] = &&l_unesc, + ['/'] = &&l_unesc, + ['b'] = &&l_unesc, + ['f'] = &&l_unesc, + ['n'] = &&l_unesc, + ['r'] = &&l_unesc, + ['t'] = &&l_unesc, + ['u'] = &&l_unesc + }; + static void **go = gostruct; + +#if 0 +printf (" gostrct= %p\n", gostruct); +printf (" gobare = %p\n", gobare); +printf (" gostr = %p\n", gostring); +printf (" goesc = %p\n", goesc); +printf (" goutf8= %p\n", goutf8_continue); +#endif + for (cur = js, end = js + len; cur < end; cur++) { + // printf (" --> %s %p\n", cur, go[*cur]); + goto *go[*cur]; + l_loop:; + } + return depth; // 0 if successful full parse, >0 for incomplete data +l_bad: + return 1; +l_up: + PUSH(0); + ++depth; + goto l_loop; +l_down: + --depth; + CAP(0); + goto l_loop; +l_qup: + PUSH(1); + go = gostring; + goto l_loop; +l_qdown: + CAP(-1); + go = gostruct; + goto l_loop; +l_esc: + go = goesc; + goto l_loop; +l_unesc: + go = gostring; + goto l_loop; +#if HAVE_RAWSTR +l_rawstr: + PUSH(0); + go = gorawstr; + goto l_loop; +#endif +l_bare: + PUSH(0); + go = gobare; + goto l_loop; +l_unbare: + CAP(-1); + go = gostruct; + goto *go[*cur]; +l_utf8_2: + go = goutf8_continue; + utf8_remain = 1; + goto l_loop; +l_utf8_3: + go = goutf8_continue; + utf8_remain = 2; + goto l_loop; +l_utf8_4: + go = goutf8_continue; + utf8_remain = 3; + goto l_loop; +l_utf_continue: + if (!--utf8_remain) + go = gostring; + goto l_loop; +} + +#else // HAVE_COMPUTED_GOTOS + +#define GO_DOWN (1) +#define GO_UP (1 << 1) +#define GO_Q_DOWN (1 << 2) +#define GO_Q_UP (1 << 3) +#define GO_BARE (1 << 4) +#define GO_UNBARE (1 << 5) +#define GO_ESCAPE (1 << 6) +#define GO_UNESCAPE (1 << 7) +#define GO_UTF8 (1 << 8) +#define GO_UTF8_CONTINUE (1 << 9) +int sdb_js0n(const ut8 *js, RangstrType len, RangstrType *out) { + ut32 prev = 0; + const ut8 *cur, *end; + int depth = 0, utf8_remain = 0, what_did = 1; + for (cur = js, end = js + len; cur < end; cur++) { + if (what_did & GO_BARE) { + switch (*cur) { + case ' ': + case '\t': + case '\r': + case '\n': + case ',': + case ']': + case '}': + what_did = GO_UNBARE; + CAP(-1); + break; + default: + if (*cur >= 32 && *cur <= 126) { + continue; + } + return 1; + } + // Same *cur + } + if (what_did & GO_UTF8) { + if (*cur < 128 || (*cur >= 192 && *cur <= 255)) { + return 1; + } + if (!--utf8_remain) { + what_did = GO_UTF8_CONTINUE; + } + continue; + } + if (what_did & GO_ESCAPE) { + switch (*cur) { + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + case 'u': + what_did = GO_UNESCAPE; + break; + default: + return 1; + } + continue; + } + if (what_did & GO_Q_UP || what_did & GO_UTF8_CONTINUE || what_did & GO_UNESCAPE) { + switch (*cur) { + case '\\': + what_did = GO_ESCAPE; + break; + case '"': + what_did = GO_Q_DOWN; + CAP(-1); + break; + default: + if (*cur <= 31 || (*cur >= 127 && *cur <= 191) || (*cur >= 248 && *cur <= 255)) { + return 1; + } + if (*cur < 127) { + continue; + } + what_did = GO_UTF8; + if (*cur < 224) { + utf8_remain = 1; + continue; + } + if (*cur < 239) { + utf8_remain = 2; + continue; + } + utf8_remain = 3; + break; + } + continue; + } + switch (*cur) { + case '\t': + case ' ': + case '\r': + case '\n': + case ',': + case ':': + break; + case '"': + PUSH(1); + what_did = GO_Q_UP; + break; + case '[': + case '{': + PUSH(0); + ++depth; + what_did = GO_UP; + break; + case ']': + case '}': + --depth; + CAP(0); + what_did = GO_DOWN; + break; + case '-': + case 't': + case 'f': + case 'n': + what_did = GO_BARE; + PUSH(0); + break; + default: + if (*cur >= 48 && *cur <= 57) { // 0-9 + what_did = GO_BARE; + PUSH(0); + break; + } + return 1; + } + } + return depth; +} +#endif // HAVE_COMPUTED_GOTOS diff --git a/librz/util/sdb/src/json/main.c b/librz/util/sdb/src/json/main.c new file mode 100644 index 00000000000..2347a59f40d --- /dev/null +++ b/librz/util/sdb/src/json/main.c @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: 2012 pancake +// SPDX-License-Identifier: MIT + +#include +#include +#include "rangstr.h" +#include "json.h" +#include "../sdb.h" + +Rangstr json_find(const char *s, Rangstr *rs); +int test_main() { + Rangstr rs; + + rs = json_get("{\"hello\":\"world\"}", "hello"); + printf("OUT: "); + rangstr_print(&rs); + printf("\n"); + + printf("--\n"); + rs = json_get("{\"hello\":{\"world\":123}}", "hello.world"); + printf("OUT: "); + rangstr_print(&rs); + printf("\n"); + return 0; +} + +int test_glossary(char *buf) { + char *path; + { + path = "glossary.title"; + char *s = api_json_set(buf, path, "patata"); + if (s) { + printf("%s\n", s); + free(s); + } else + printf("set failed\n"); + } + { + path = "glossary.title"; + char *s = api_json_get(buf, path); + if (s) { + printf("%s\n", s); + free(s); + } else + printf("set failed\n"); + } + { + path = "glossary.title"; + char *s = api_json_get(buf, path); + if (s) { + printf("%s\n", s); + free(s); + } else + printf("set failed\n"); + } + return 0; +} + +int main(int argc, char **argv) { + Rangstr rs; + char buf[4096]; + int n = fread(buf, 1, sizeof(buf), stdin); + buf[n] = 0; + // return test_glossary (buf); + // return test_main (); + char *path = argv[1]; + +#if 1 + printf(">>>> %s <<<<\n", sdb_json_unindent(buf)); + printf(">>>> %s <<<<\n", sdb_json_indent(buf, " ")); + // set value // + path = "glossary.title"; + char *s = api_json_set(buf, path, "patata"); + if (s) { + printf("%s\n", s); + free(s); + } else + printf("set failed\n"); +#endif +// printf ("%s\n", str); return 0; + +// s = "[1,3,4]"; +#if 0 + char *a = "{}"; + a = json_seti (a, "id", 123); + a = json_seti (a, "user.name", "blah"); + printf ("id = %d\n", json_geti ("{'id':123}", "id")); +#endif + // json_walk (buf); + + path = argv[1]; + rs = json_get(buf, path); + printf("-- output --\n"); + rangstr_print(&rs); + printf("\n"); + return 0; +} diff --git a/librz/util/sdb/src/json/path.c b/librz/util/sdb/src/json/path.c new file mode 100644 index 00000000000..d33070c2e21 --- /dev/null +++ b/librz/util/sdb/src/json/path.c @@ -0,0 +1,176 @@ +// SPDX-FileCopyrightText: 2012-2017 pancake +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include "rangstr.c" +#include + +RZ_IPI void json_path_first(Rangstr *s) { + char *p; + if (!s->p) { + return; + } + p = strchr(s->p, '.'); + s->f = 0; + s->t = p ? (size_t)(p - s->p) : strlen(s->p); +} + +RZ_IPI int json_path_next(Rangstr *s) { + int stop = '.'; + if (!s || !s->p || !s->p[s->t]) { + return 0; + } + if (!s->next) { + return 0; + } + if (s->p[s->t] == '"') { + s->t++; + } +rep: + if (s->p[s->t] == '[') { + s->type = '['; + stop = ']'; + } else { + s->type = 0; + } + s->f = ++s->t; + if (s->p[s->t] == stop) { + s->f = ++s->t; + } + if (!s->p[s->t]) { + return 0; + } + while (s->p[s->t] != stop) { + if (!s->p[s->t]) { + s->next = 0; + return 1; + } + if (s->p[s->t] == '[') { + break; + } + s->t++; + } + if (s->f == s->t) { + goto rep; + } + if (s->p[s->f] == '"') { + s->f++; + s->t--; + } + return 1; +} + +RZ_IPI Rangstr json_find(const char *s, Rangstr *rs) { +#define RESFIXSZ 1024 + RangstrType resfix[RESFIXSZ] = { 0 }; + RangstrType *res = resfix; + int i, j, n, len, ret; + Rangstr rsn; + + if (!s) { + return rangstr_null(); + } + + len = strlen(s); + if (len > RESFIXSZ) { + res = calloc(len + 1, sizeof(RangstrType)); + if (!res) { + eprintf("Cannot allocate %d byte%s\n", + len + 1, (len > 1) ? "s" : ""); + return rangstr_null(); + } + } + + ret = sdb_js0n((const unsigned char *)s, len, res); +#define PFREE(x) \ + if (x && x != resfix) \ + free(x) + if (ret > 0) { + PFREE(res); + return rangstr_null(); + } + + if (*s == '[') { + n = rangstr_int(rs); + if (n < 0) { + goto beach; + } + + for (i = j = 0; res[i] && j < n; i += 2, j++) + ; + if (!res[i]) { + goto beach; + } + + rsn = rangstr_news(s, res, i); + + PFREE(res); + return rsn; + } else { + for (i = 0; res[i]; i += 4) { + Rangstr rsn = rangstr_news(s, res, i); + if (!rangstr_cmp(rs, &rsn)) { + rsn = rangstr_news(s, res, i + 2); + PFREE(res); + return rsn; + } + } + } +beach: + PFREE(res); + return rangstr_null(); +} + +RZ_IPI Rangstr json_get(const char *js, const char *p) { + int x, n = 0; + size_t rst; + Rangstr rj2, rj = rangstr_new(js); + Rangstr rs = rangstr_new(p); + json_path_first(&rs); + do { + rst = rs.t; + rs.f++; + x = rangstr_find(&rs, '['); + rs.f--; + if (x != -1) + rs.t = x; +#if 0 +printf ("x = %d f = %d t = %d\n", x, rs.f, rs.t); +fprintf (stderr, "source (%s)\n", rangstr_dup (&rs)); +fprintf (stderr, "onjson (%s)\n", rangstr_dup (&rj)); +#endif + if (rst == rs.t && n && rj.p) // last key + break; + if (!rj.p) + break; + do { + rj2 = json_find(rangstr_str(&rj), &rs); + // fprintf (stderr, "++ (%s)(%d vs %d)\n", rangstr_dup (&rs), x, rs.t); + // if (rj.p[rj.f]=='[') { break; } + // fprintf (stderr, "ee %c\n", rj.p[rj.f]); + if (!rj2.p) { + if (!rj.p[rj.t]) + return rj2; + break; + } + rj = rj2; +#if 0 +fprintf (stderr, "-- (%s)\n", rangstr_dup (&rj)); +#endif + } while (json_path_next(&rs)); +// if (!rj.p) return rj; +#if 0 +printf ("x = %d\n", x); printf ("rsf = %d\n", rs.f); +fprintf (stderr, "xxx (%s)\n", rangstr_dup (&rj)); +return rj; +#endif + if ((rst == rs.t && n && rj.p)) // last key + break; + rs.t = rst; + rs.f = x; + n++; + } while (x != -1); + return rj; +} diff --git a/librz/util/sdb/src/json/rangstr.c b/librz/util/sdb/src/json/rangstr.c new file mode 100644 index 00000000000..624d0faf883 --- /dev/null +++ b/librz/util/sdb/src/json/rangstr.c @@ -0,0 +1,121 @@ +// SPDX-FileCopyrightText: 2012-2017 pancake +// SPDX-License-Identifier: MIT + +#ifndef RANGSTR_C +#define RANGSTR_C + +#include +#include +#include +#include "rangstr.h" + +#if 0 +RZ_IPI void rangstr_print (Rangstr *s) { + if (s && s->p) { + (void) fwrite (s->p+s->f, + s->t-s->f, 1, stdout); + } +} +#endif + +RZ_IPI Rangstr rangstr_null(void) { + Rangstr rs = { 0, 0, 0, 0, 0 }; + return rs; +} + +RZ_IPI Rangstr rangstr_new(const char *s) { + Rangstr rs; + if (!s) { + return rangstr_null(); + } + rs.f = 0; + rs.next = 1; + rs.t = strlen(s); + rs.p = s; + rs.type = 0; + return rs; +} + +RZ_IPI int rangstr_length(Rangstr *rs) { + if (rs->t > rs->f) { + return rs->t - rs->f; + } + return 0; +} + +RZ_IPI int rangstr_int(Rangstr *s) { + if (!s || !s->p) { + return 0; + } + + const int base = 10; + int mul = 1; + int ch, n = 0; + size_t i = 0; + if (s->p[s->f] == '[') { + i++; + } + if (s->p[s->f] == '-') { + mul = -1; + i += s->f + 1; + } else { + i += s->f; + } + for (; i < s->t; i++) { + ch = s->p[i]; + if (ch < '0' || ch > '9') { + break; + } + n = n * base + (ch - '0'); + } + return n * mul; +} + +RZ_IPI char *rangstr_dup(Rangstr *rs) { + if (!rs->p) { + return NULL; + } + int len = rangstr_length(rs); + char *p = malloc(len + 1); + if (p) { + memcpy(p, rs->p + rs->f, len); + p[len] = 0; + } + return p; +} + +RZ_IPI Rangstr rangstr_news(const char *s, RangstrType *res, int i) { + Rangstr rs; + rs.next = 1; + rs.f = res[i]; + rs.t = res[i] + res[i + 1]; + rs.p = s; + rs.type = 0; + return rs; +} + +RZ_IPI int rangstr_cmp(Rangstr *a, Rangstr *b) { + int la = a->t - a->f; + int lb = b->t - b->f; + int lbz = strlen(b->p + b->f); + if (lbz < lb) { + lb = lbz; + } + if (la != lb) { + return 1; + } + return memcmp(a->p + a->f, b->p + b->f, la); +} + +RZ_IPI int rangstr_find(Rangstr *a, char ch) { + size_t i = a->f; + while (i < a->t && a->p[i] && a->p[i] != ch) + i++; + return (i < a->t && a->p[i]) ? (int)i : -1; +} + +RZ_IPI const char *rangstr_str(Rangstr *rs) { + return rs->p + rs->f; +} + +#endif diff --git a/librz/util/sdb/src/json/rangstr.h b/librz/util/sdb/src/json/rangstr.h new file mode 100644 index 00000000000..3e11a3dabab --- /dev/null +++ b/librz/util/sdb/src/json/rangstr.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#ifndef _INCLUDE_RANGSTR_H_ +#define _INCLUDE_RANGSTR_H_ + +#include +#include + +#define RangstrType unsigned int + +typedef struct { + int type; + int next; + size_t f, t; + const char *p; +} Rangstr; + +#if 0 +RZ_IPI void rangstr_print (Rangstr *s); +RZ_IPI Rangstr rangstr_new (const char *s); +RZ_IPI Rangstr rangstr_null(void); +RZ_IPI int rangstr_int (Rangstr *s); +RZ_IPI char *rangstr_dup (Rangstr *rs); +RZ_IPI Rangstr rangstr_news (const char *s, RangstrType *res, int i); +RZ_IPI int rangstr_cmp (Rangstr *a, Rangstr *b); +RZ_IPI const char *rangstr_str (Rangstr* rs); +RZ_IPI int rangstr_length (Rangstr* rs); +RZ_IPI int rangstr_find (Rangstr* rs, char ch); +#endif + +#endif diff --git a/librz/util/sdb/src/json/test.c b/librz/util/sdb/src/json/test.c new file mode 100644 index 00000000000..46e9b7c173b --- /dev/null +++ b/librz/util/sdb/src/json/test.c @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2012 pancake +// SPDX-License-Identifier: MIT + +int test_parse_json_path() { + Rangstr rs = rangstr_new("ping.board[\"pop\"][1][2][\"caca\"].panda"); + json_path_first(&rs); + do { + printf(" - "); + rangstr_print(&rs); + printf(" Int (%d)", rangstr_int(&rs)); + printf("\n"); + } while (json_path_next(&rs)); + printf("--\n"); +} diff --git a/librz/util/sdb/src/json/test.json b/librz/util/sdb/src/json/test.json new file mode 100644 index 00000000000..73c12da0fb1 --- /dev/null +++ b/librz/util/sdb/src/json/test.json @@ -0,0 +1,4 @@ +{ "glossary": { "title": "example glossary", "page": 1, "GlossDiv": { "title": "First gloss title", +"GlossList": { "GlossEntry": { "ID": "SGML", "SortAs": "SGML", "GlossTerm": "Standard Generalized Markup Language", +"Acronym": "SGML", "Abbrev": "ISO 8879:1986", "GlossDef": { "para": "A meta-markup language, used to create markup languages such as DocBook.", +"GlossSeeAlso": [ "OK", "GML", "XML" ] }, "GlossSee": "markup" } } } } } diff --git a/librz/util/sdb/src/lock.c b/librz/util/sdb/src/lock.c new file mode 100644 index 00000000000..3ae3acaa1dd --- /dev/null +++ b/librz/util/sdb/src/lock.c @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2012-2016 pancake +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include "sdb.h" +#if __WINDOWS__ +#include +#endif + +RZ_API const char *sdb_lock_file(const char *f) { + static char buf[128]; + size_t len; + if (!f || !*f) { + return NULL; + } + len = strlen(f); + if (len + 10 > sizeof buf) { + return NULL; + } + memcpy(buf, f, len); + strcpy(buf + len, ".lock"); + return buf; +} + +RZ_API bool sdb_lock(const char *s) { + int fd; + char *pid, pidstr[64]; + if (!s) { + return false; + } + fd = open(s, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL, SDB_MODE); + if (fd == -1) { + return false; + } + pid = sdb_itoa(rz_sys_getpid(), pidstr, 10); + if (pid) { + if ((write(fd, pid, strlen(pid)) < 0) || (write(fd, "\n", 1) < 0)) { + close(fd); + return false; + } + } + close(fd); + return true; +} + +RZ_API int sdb_lock_wait(const char *s) { + // TODO use flock() here + // wait forever here? + while (!sdb_lock(s)) { + // TODO: if waiting too much return 0 +#if __WINDOWS__ + Sleep(500); // hack +#else + // TODO use lockf() here .. flock is not much useful (fd, LOCK_EX); + sleep(1); // hack +#endif + } + return 1; +} + +RZ_API void sdb_unlock(const char *s) { + // flock (fd, LOCK_UN); + unlink(s); +} + +#if TEST +main() { + int r; + r = sdb_lock(".lock"); + printf("%d\n", r); + r = sdb_lock(".lock"); + printf("%d\n", r); + sdb_unlock(".lock"); + r = sdb_lock(".lock"); + printf("%d\n", r); +} +#endif diff --git a/librz/util/sdb/src/ls.c b/librz/util/sdb/src/ls.c new file mode 100644 index 00000000000..1d5f1a12e98 --- /dev/null +++ b/librz/util/sdb/src/ls.c @@ -0,0 +1,378 @@ +// SPDX-FileCopyrightText: 2007-2017 pancake +// SPDX-FileCopyrightText: 2007-2017 alvaro +// SPDX-License-Identifier: MIT + +#include +#include "ls.h" + +RZ_API SdbList *ls_newf(SdbListFree freefn) { + SdbList *list = ls_new(); + if (list) { + list->free = freefn; + } + return list; +} + +RZ_API SdbList *ls_new(void) { + SdbList *list = RZ_NEW0(SdbList); + if (!list) { + return NULL; + } + return list; +} + +static void ls_insertion_sort_iter(SdbListIter *iter, SdbListComparator cmp) { + SdbListIter *it, *it2; + for (it = iter; it && it->data; it = it->n) { + for (it2 = it->n; it2 && it2->data; it2 = it2->n) { + if (cmp(it->data, it2->data) > 0) { + void *t = it->data; + it->data = it2->data; + it2->data = t; + } + } + } +} + +static void ls_insertion_sort(SdbList *list, SdbListComparator cmp) { + ls_insertion_sort_iter(list->head, cmp); +} + +static SdbListIter *_merge(SdbListIter *first, SdbListIter *second, SdbListComparator cmp) { + SdbListIter *next = NULL, *result = NULL, *head = NULL; + while (first || second) { + if (!second) { + next = first; + first = first->n; + } else if (!first) { + next = second; + second = second->n; + } else if (cmp(first->data, second->data) <= 0) { + next = first; + first = first->n; + } else { + next = second; + second = second->n; + } + if (!head) { + result = next; + head = result; + head->p = NULL; + } else { + result->n = next; + next->p = result; + result = result->n; + } + } + head->p = NULL; + next->n = NULL; + return head; +} + +static SdbListIter *_sdb_list_split(SdbListIter *head) { + SdbListIter *tmp; + SdbListIter *fast; + SdbListIter *slow; + if (!head || !head->n) { + return head; + } + slow = head; + fast = head; + while (fast && fast->n && fast->n->n) { + fast = fast->n->n; + slow = slow->n; + } + tmp = slow->n; + slow->n = NULL; + return tmp; +} + +static SdbListIter *_merge_sort(SdbListIter *head, SdbListComparator cmp) { + SdbListIter *second; + if (!head || !head->n) { + return head; + } + second = _sdb_list_split(head); + head = _merge_sort(head, cmp); + second = _merge_sort(second, cmp); + return _merge(head, second, cmp); +} + +RZ_API bool ls_merge_sort(SdbList *list, SdbListComparator cmp) { + if (!cmp) { + return false; + } + if (list && list->head && cmp) { + SdbListIter *iter; + list->head = _merge_sort(list->head, cmp); + // update tail reference + iter = list->head; + while (iter && iter->n) { + iter = iter->n; + } + list->tail = iter; + list->sorted = true; + } + return true; +} + +RZ_API bool ls_sort(SdbList *list, SdbListComparator cmp) { + if (!cmp || list->cmp == cmp) { + return false; + } + if (list->length > 43) { + ls_merge_sort(list, cmp); + } else { + ls_insertion_sort(list, cmp); + } + list->cmp = cmp; + list->sorted = true; + return true; +} + +RZ_API void ls_delete(SdbList *list, SdbListIter *iter) { + if (!list || !iter) { + return; + } + ls_split_iter(list, iter); + if (list->free && iter->data) { + list->free(iter->data); + iter->data = NULL; + } + free(iter); +} + +RZ_API bool ls_delete_data(SdbList *list, void *ptr) { + void *kvp; + SdbListIter *iter; + ls_foreach (list, iter, kvp) { + if (ptr == kvp) { + ls_delete(list, iter); + return true; + } + } + return false; +} + +RZ_API void ls_split_iter(SdbList *list, SdbListIter *iter) { + if (!list || !iter) { + return; + } + if (list->head == iter) { + list->head = iter->n; + } + if (list->tail == iter) { + list->tail = iter->p; + } + if (iter->p) { + iter->p->n = iter->n; + } + if (iter->n) { + iter->n->p = iter->p; + } + list->length--; +} + +RZ_API void ls_destroy(SdbList *list) { + SdbListIter *it; + if (!list) { + return; + } + it = list->head; + while (it) { + SdbListIter *next = it->n; + ls_delete(list, it); + it = next; + } + list->head = list->tail = NULL; + list->length = 0; +} + +RZ_API void ls_free(SdbList *list) { + if (!list) { + return; + } + ls_destroy(list); + list->free = NULL; + free(list); +} + +RZ_API SdbListIter *ls_append(SdbList *list, void *data) { + SdbListIter *it; + if (!list) { + return NULL; + } + it = RZ_NEW(SdbListIter); + if (!it) { + return NULL; + } + if (list->tail) { + list->tail->n = it; + } + it->data = data; + it->p = list->tail; + it->n = NULL; + list->tail = it; + if (!list->head) { + list->head = it; + } + list->length++; + list->sorted = false; + return it; +} + +RZ_API SdbListIter *ls_prepend(SdbList *list, void *data) { + SdbListIter *it = RZ_NEW(SdbListIter); + if (!it) { + return NULL; + } + if (list->head) { + list->head->p = it; + } + it->data = data; + it->n = list->head; + it->p = NULL; + list->head = it; + if (!list->tail) { + list->tail = it; + } + list->length++; + list->sorted = false; + return it; +} + +RZ_API void *ls_pop(SdbList *list) { + void *data = NULL; + SdbListIter *iter; + if (list) { + if (list->tail) { + iter = list->tail; + if (list->head == list->tail) { + list->head = list->tail = NULL; + } else { + list->tail = iter->p; + list->tail->n = NULL; + } + data = iter->data; + free(iter); + list->length--; + } + return data; + } + return NULL; +} + +RZ_API SdbList *ls_clone(SdbList *list) { + if (!list) { + return NULL; + } + SdbList *r = ls_new(); // ownership of elements stays in original list + if (!r) { + return NULL; + } + void *v; + SdbListIter *iter; + ls_foreach (list, iter, v) { + ls_append(r, v); + } + return r; +} + +RZ_API int ls_join(SdbList *list1, SdbList *list2) { + if (!list1 || !list2) { + return 0; + } + if (!(list2->length)) { + return 0; + } + if (!(list1->length)) { + list1->head = list2->head; + list1->tail = list2->tail; + } else { + list1->tail->n = list2->head; + list2->head->p = list1->tail; + list1->tail = list2->tail; + list1->tail->n = NULL; + } + list1->length += list2->length; + list2->head = list2->tail = NULL; + list1->sorted = false; + return 1; +} + +RZ_API SdbListIter *ls_insert(SdbList *list, int n, void *data) { + SdbListIter *it, *item; + int i; + if (list) { + if (!list->head || !n) { + return ls_prepend(list, data); + } + for (it = list->head, i = 0; it && it->data; it = it->n, i++) { + if (i == n) { + item = RZ_NEW0(SdbListIter); + if (!item) { + return NULL; + } + item->data = data; + item->n = it; + item->p = it->p; + if (it->p) { + it->p->n = item; + } + it->p = item; + list->length++; + list->sorted = false; + return item; + } + } + } + return ls_append(list, data); +} + +RZ_API void *ls_pop_head(SdbList *list) { + void *data = NULL; + SdbListIter *iter; + if (list) { + if (list->head) { + iter = list->head; + if (list->head == list->tail) { + list->head = list->tail = NULL; + } else { + list->head = iter->n; + list->head->p = NULL; + } + data = iter->data; + free(iter); + } + list->length--; + return data; + } + return NULL; +} + +RZ_API int ls_del_n(SdbList *list, int n) { + SdbListIter *it; + int i; + if (!list) { + return false; + } + for (it = list->head, i = 0; it && it->data; it = it->n, i++) + if (i == n) { + if (!it->p && !it->n) { + list->head = list->tail = NULL; + } else if (!it->p) { + it->n->p = NULL; + list->head = it->n; + } else if (!it->n) { + it->p->n = NULL; + list->tail = it->p; + } else { + it->p->n = it->n; + it->n->p = it->p; + } + free(it); + list->length--; + return true; + } + return false; +} diff --git a/librz/util/sdb/src/ls.h b/librz/util/sdb/src/ls.h new file mode 100644 index 00000000000..6679343d5d4 --- /dev/null +++ b/librz/util/sdb/src/ls.h @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#ifndef LS_H +#define LS_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*SdbListFree)(void *ptr); +typedef int (*SdbListComparator)(const void *a, const void *b); + +typedef struct ls_iter_t { + void *data; + struct ls_iter_t *n, *p; +} SdbListIter; + +typedef struct ls_t { + size_t length; + SdbListIter *head; + SdbListIter *tail; + SdbListFree free; + SdbListComparator cmp; + bool sorted; +} SdbList; + +#define ls_foreach(list, it, pos) \ + if ((list)) \ + for (it = (list)->head; it && (pos = it->data); it = it->n) +#define ls_foreach_safe(list, it, tmp, pos) \ + if ((list)) \ + for (it = list->head; \ + it && (pos = it->data) && ((tmp = it->n) || 1); it = tmp) +#define ls_foreach_prev(list, it, pos) \ + if ((list)) \ + for (it = list->tail; it && (pos = it->data); it = it->p) + +#define ls_iterator(x) (x) ? (x)->head : NULL +// #define ls_empty(x) (!x || (!x->head && !x->tail)) +#define ls_empty(x) (!x || !x->length) +#define ls_head(x) x->head +#define ls_tail(x) x->tail +#define ls_unref(x) x +#define ls_iter_get(x) \ + x->data; \ + x = x->n +#define ls_iter_next(x) (x ? 1 : 0) +#define ls_iter_cur(x) x->p +#define ls_iter_unref(x) x +#define ls_length(x) x->length +RZ_API SdbList *ls_new(void); +RZ_API SdbList *ls_newf(SdbListFree freefn); +RZ_API SdbListIter *ls_append(SdbList *list, void *data); +RZ_API SdbListIter *ls_prepend(SdbList *list, void *data); +// RZ_API void ls_add_sorted(SdbList *list, void *data, SdbListComparator cmp); +RZ_API bool ls_sort(SdbList *list, SdbListComparator cmp); +RZ_API bool ls_merge_sort(SdbList *list, SdbListComparator cmp); + +RZ_API void ls_delete(SdbList *list, SdbListIter *iter); +RZ_API bool ls_delete_data(SdbList *list, void *ptr); +RZ_API void ls_iter_init(SdbListIter *iter, SdbList *list); +RZ_API void ls_destroy(SdbList *list); +RZ_API void ls_free(SdbList *list); +RZ_API SdbListIter *ls_item_new(void *data); +RZ_API void ls_unlink(SdbList *list, void *ptr); +RZ_API void ls_split(SdbList *list, void *ptr); +// Removes element `iter` from `list`. +RZ_API void ls_split_iter(SdbList *list, SdbListIter *iter); +RZ_API void *ls_get_n(SdbList *list, int n); +RZ_API void *ls_get_top(SdbList *list); +#define ls_push(x, y) ls_append(x, y) +RZ_API void *ls_pop(SdbList *list); +RZ_API void ls_reverse(SdbList *list); +RZ_API SdbList *ls_clone(SdbList *list); +RZ_API int ls_join(SdbList *first, SdbList *second); +RZ_API int ls_del_n(SdbList *list, int n); +RZ_API SdbListIter *ls_insert(SdbList *list, int n, void *data); +RZ_API void *ls_pop_head(SdbList *list); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librz/util/sdb/src/main.c b/librz/util/sdb/src/main.c new file mode 100644 index 00000000000..082af5a4c0e --- /dev/null +++ b/librz/util/sdb/src/main.c @@ -0,0 +1,532 @@ +// SPDX-FileCopyrightText: 2011-2020 pancake +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include "sdb.h" +#include "sdb_private.h" + +#define MODE_ZERO '0' +#define MODE_JSON 'j' +#define MODE_DFLT 0 + +static int save = 0; +static Sdb *s = NULL; +static ut32 options = SDB_OPTION_FS | SDB_OPTION_NOSTAMP; + +static void terminate(RZ_UNUSED int sig) { + if (!s) { + return; + } + if (save && !sdb_sync(s)) { + sdb_free(s); + s = NULL; + exit(1); + } + sdb_free(s); + exit(sig < 2 ? sig : 0); +} + +static void write_null(void) { + write_(1, "", 1); +} + +#define BS 128 +#define USE_SLURPIN 1 + +static char *slurp(FILE *f, size_t *sz) { + int blocksize = BS; + static int bufsize = BS; + static char *next = NULL; + static size_t nextlen = 0; + size_t len, rr, rr2; + char *tmp, *buf = NULL; + if (sz) { + *sz = 0; + } +#if USE_SLURPIN + if (!sz) { + /* this is faster but have limits */ + /* run test/add10k.sh script to benchmark */ + const int buf_size = 96096; + + buf = calloc(1, buf_size); + if (!buf) { + return NULL; + } + + if (!fgets(buf, buf_size, f)) { + free(buf); + return NULL; + } + if (feof(f)) { + free(buf); + return NULL; + } + + size_t buf_len = strlen(buf); + if (buf_len > 0) { + buf[buf_len - 1] = '\0'; + } + + char *newbuf = realloc(buf, buf_len + 1); + // realloc behaves like free if buf_len is 0 + if (!newbuf) { + return buf; + } + return newbuf; + } +#endif + buf = calloc(BS + 1, 1); + if (!buf) { + return NULL; + } + + len = 0; + for (;;) { + if (next) { + free(buf); + buf = next; + bufsize = nextlen + blocksize; + // len = nextlen; + rr = nextlen; + rr2 = fread(buf + nextlen, 1, blocksize, f); + if (rr2 > 0) { + rr += rr2; + bufsize += rr2; + } + next = NULL; + nextlen = 0; + } else { + rr = fread(buf + len, 1, blocksize, f); + } + if (rr < 1) { // EOF + buf[len] = 0; + next = NULL; + break; + } + len += rr; + // buf[len] = 0; +#if !USE_SLURPIN + if (!sz) { + char *nl = strchr(buf, '\n'); + if (nl) { + *nl++ = 0; + int nlen = nl - buf; + nextlen = len - nlen; + if (nextlen > 0) { + next = malloc(nextlen + blocksize + 1); + if (!next) { + eprintf("Cannot malloc %d\n", nextlen); + break; + } + memcpy(next, nl, nextlen); + if (!*next) { + next = NULL; + } else { + // continue; + } + } else { + next = NULL; + nextlen = 0; // strlen (next);; + } + break; + } + } +#endif + bufsize += blocksize; + tmp = realloc(buf, bufsize + 1); + if (!tmp) { + bufsize -= blocksize; + break; + } + memset(tmp + bufsize - blocksize, 0, blocksize); + buf = tmp; + } + if (sz) { + *sz = len; + } + if (len < 1) { + free(buf); + return buf = NULL; + } + buf[len] = 0; + return buf; +} + +#if HAVE_HEADER_SYS_MMAN_H +static void synchronize(RZ_UNUSED int sig) { + // TODO: must be in sdb_sync() or wat? + sdb_sync(s); + Sdb *n = sdb_new(s->path, s->name, s->lock); + if (n) { + sdb_config(n, options); + sdb_free(s); + s = n; + } +} +#endif + +static int sdb_grep_dump(const char *dbname, int fmt, bool grep, + const char *expgrep) { + char *v, k[SDB_CDB_MAX_KEY] = { 0 }; + const char *comma = ""; + // local db beacuse is readonly and we dont need to finalize in case of ^C + Sdb *db = sdb_new(NULL, dbname, 0); + if (!db) { + return 1; + } + sdb_config(db, options); + sdb_dump_begin(db); + if (fmt == MODE_JSON) { + printf("{"); + } + while (sdb_dump_dupnext(db, k, &v, NULL)) { + if (grep && !strstr(k, expgrep) && !strstr(v, expgrep)) { + free(v); + continue; + } + switch (fmt) { + case MODE_JSON: + if (!strcmp(v, "true") || !strcmp(v, "false")) { + printf("%s\"%s\":%s", comma, k, v); + } else if (sdb_isnum(v)) { + printf("%s\"%s\":%" PFMT64x "u", comma, k, sdb_atoi(v)); + } else if (*v == '{' || *v == '[') { + printf("%s\"%s\":%s", comma, k, v); + } else { + printf("%s\"%s\":\"%s\"", comma, k, v); + } + comma = ","; + break; + case MODE_ZERO: + printf("%s=%s", k, v); + break; + default: + printf("%s=%s\n", k, v); + break; + } + free(v); + } + switch (fmt) { + case MODE_ZERO: + fflush(stdout); + write_null(); + break; + case MODE_JSON: + printf("}\n"); + break; + } + sdb_free(db); + return 0; +} + +static int sdb_grep(const char *db, int fmt, const char *grep) { + return sdb_grep_dump(db, fmt, true, grep); +} + +static int sdb_dump(const char *db, int fmt) { + return sdb_grep_dump(db, fmt, false, NULL); +} + +static int insertkeys(Sdb *s, const char **args, int nargs, int mode) { + int must_save = 0; + if (args && nargs > 0) { + int i; + for (i = 0; i < nargs; i++) { + switch (mode) { + case '-': + must_save |= sdb_query(s, args[i]); + break; + case '=': + if (strchr(args[i], '=')) { + char *v, *kv = (char *)strdup(args[i]); + v = strchr(kv, '='); + if (v) { + *v++ = 0; + sdb_disk_insert(s, kv, v); + } + free(kv); + } + break; + } + } + } + return must_save; +} + +static int createdb(const char *f, const char **args, int nargs) { + s = sdb_new(NULL, f, 0); + if (!s) { + eprintf("Cannot create database\n"); + return 1; + } + sdb_config(s, options); + int ret = 0; + if (args) { + int i; + for (i = 0; i < nargs; i++) { + if (!sdb_text_load(s, args[i])) { + eprintf("Failed to load text sdb from %s\n", args[i]); + } + } + } else { + size_t len; + char *in = slurp(stdin, &len); + if (!in) { + return 0; + } + if (!sdb_text_load_buf(s, in, len)) { + eprintf("Failed to read text sdb from stdin\n"); + } + free(in); + } + sdb_sync(s); + return ret; +} + +static int showusage(int o) { + printf("usage: sdb [-0cdehjJv|-D A B] [-|db] " + "[.file]|[-=]|==||[-+][(idx)key[:json|=value] ..]\n"); + if (o == 2) { + printf(" -0 terminate results with \\x00\n" + " -c count the number of keys database\n" + " -d decode base64 from stdin\n" + " -D diff two databases\n" + " -e encode stdin as base64\n" + " -h show this help\n" + " -j output in json\n" + " -J enable journaling\n" + " -v show version information\n"); + return 0; + } + return o; +} + +static int showversion(void) { + fflush(stdout); + return 0; +} + +static int jsonIndent(void) { + size_t len; + char *out; + char *in = slurp(stdin, &len); + if (!in) { + return 0; + } + out = sdb_json_indent(in, " "); + if (!out) { + free(in); + return 1; + } + puts(out); + free(out); + free(in); + return 0; +} + +static int base64encode(void) { + char *out; + size_t len = 0; + ut8 *in = (ut8 *)slurp(stdin, &len); + if (!in) { + return 0; + } + out = sdb_encode(in, (int)len); + if (!out) { + free(in); + return 1; + } + puts(out); + free(out); + free(in); + return 0; +} + +static int base64decode(void) { + ut8 *out; + size_t len, ret = 1; + char *in = slurp(stdin, &len); + if (in) { + int declen; + out = sdb_decode(in, &declen); + if (out && declen >= 0) { + write_(1, out, declen); + ret = 0; + } + free(out); + free(in); + } + return ret; +} + +static void dbdiff_cb(const SdbDiff *diff, void *user) { + char sbuf[512]; + int r = sdb_diff_format(sbuf, sizeof(sbuf), diff); + if (r < 0) { + return; + } + char *buf = sbuf; + char *hbuf = NULL; + if ((size_t)r >= sizeof(sbuf)) { + hbuf = malloc(r + 1); + if (!hbuf) { + return; + } + r = sdb_diff_format(hbuf, r + 1, diff); + if (r < 0) { + goto beach; + } + } + printf("\x1b[%sm%s\x1b[0m\n", diff->add ? "32" : "31", buf); +beach: + free(hbuf); +} + +static bool dbdiff(const char *a, const char *b) { + Sdb *A = sdb_new(NULL, a, 0); + Sdb *B = sdb_new(NULL, b, 0); + bool equal = sdb_diff(A, B, dbdiff_cb, NULL); + sdb_free(A); + sdb_free(B); + return equal; +} + +int showcount(const char *db) { + ut32 d; + s = sdb_new(NULL, db, 0); + if (sdb_stats(s, &d, NULL)) { + printf("%d\n", d); + } + // TODO: show version, timestamp information + sdb_free(s); + return 0; +} + +int main(int argc, const char **argv) { + char *line; + const char *arg, *grep = NULL; + int i, fmt = MODE_DFLT; + int db0 = 1, argi = 1; + bool interactive = false; + + /* terminate flags */ + if (argc < 2) { + return showusage(1); + } + arg = argv[1]; + + if (arg[0] == '-') { // && arg[1] && arg[2]==0) { + switch (arg[1]) { + case 0: + /* no-op */ + break; + case '0': + fmt = MODE_ZERO; + db0++; + argi++; + if (db0 >= argc) { + return showusage(1); + } + break; + case 'g': + db0 += 2; + if (db0 >= argc) { + return showusage(1); + } + grep = argv[2]; + argi += 2; + break; + case 'J': + options |= SDB_OPTION_JOURNAL; + db0++; + argi++; + if (db0 >= argc) { + return showusage(1); + } + break; + case 'c': return (argc < 3) ? showusage(1) : showcount(argv[2]); + case 'v': return showversion(); + case 'h': return showusage(2); + case 'e': return base64encode(); + case 'd': return base64decode(); + case 'D': + if (argc == 4) { + return dbdiff(argv[2], argv[3]) ? 0 : 1; + } + return showusage(0); + case 'j': + if (argc > 2) { + return sdb_dump(argv[db0 + 1], MODE_JSON); + } + return jsonIndent(); + default: + eprintf("Invalid flag %s\n", arg); + break; + } + } + + /* sdb - */ + if (argi == 1 && !strcmp(argv[argi], "-")) { + /* no database */ + argv[argi] = ""; + if (argc == db0 + 1) { + interactive = true; + /* if no argument passed */ + argv[argi] = "-"; + argc++; + argi++; + } + } + /* sdb dbname */ + if (argc - 1 == db0) { + if (grep) { + return sdb_grep(argv[db0], fmt, grep); + } + return sdb_dump(argv[db0], fmt); + } +#if HAVE_HEADER_SYS_MMAN_H + signal(SIGINT, terminate); + signal(SIGHUP, synchronize); +#endif + int ret = 0; + if (interactive || !strcmp(argv[db0 + 1], "-")) { + if ((s = sdb_new(NULL, argv[db0], 0))) { + sdb_config(s, options); + int kvs = db0 + 2; + if (kvs < argc) { + save |= insertkeys(s, argv + argi + 2, argc - kvs, '-'); + } + for (; (line = slurp(stdin, NULL));) { + save |= sdb_query(s, line); + if (fmt) { + fflush(stdout); + write_null(); + } + free(line); + } + } + } else if (!strcmp(argv[db0 + 1], "=")) { + ret = createdb(argv[db0], NULL, 0); + } else if (!strcmp(argv[db0 + 1], "==")) { + ret = createdb(argv[db0], argv + db0 + 2, argc - (db0 + 2)); + } else { + s = sdb_new(NULL, argv[db0], 0); + if (!s) { + return 1; + } + sdb_config(s, options); + for (i = db0 + 1; i < argc; i++) { + save |= sdb_query(s, argv[i]); + if (fmt) { + fflush(stdout); + write_null(); + } + } + } + terminate(ret); + return ret; +} diff --git a/librz/util/sdb/src/match.c b/librz/util/sdb/src/match.c new file mode 100644 index 00000000000..45827ef7f75 --- /dev/null +++ b/librz/util/sdb/src/match.c @@ -0,0 +1,120 @@ +// SPDX-FileCopyrightText: 2015-2016 pancake +// SPDX-License-Identifier: MIT + +#include "sdb.h" +#include + +static inline int haveSuffix(const char *glob, int glob_len, const char *sfx) { + const int sfx_len = strlen(sfx); + return (glob_len > sfx_len && !strcmp(glob + glob_len - sfx_len, sfx)); +} + +static inline int havePrefix(const char *glob, int glob_len, const char *pfx) { + const int pfx_len = strlen(pfx); + return (pfx_len < glob_len && !strncmp(glob, pfx, pfx_len)); +} + +enum MatchFlag { + SDB_LIKE_NONE = 0, + SDB_LIKE_ICASE = 1, // ?i + SDB_LIKE_START = 2, // ^ + SDB_LIKE_END = 4, // $ + SDB_LIKE_BASE64 = 8 // % +}; + +static inline int mycmp(const char *a, const char *b, int n, int any) { + int i, j; + for (i = j = 0; a[i] && b[j] && j < n; i++) { + if (tolower((const ut8)a[i]) == tolower((const ut8)b[j])) { + j++; + } else { + if (!any) { + return 0; + } + j = 0; + } + } + return any ? j != n : 1; +} + +static inline int strstr2(const char *a, const char *b, int n) { + int i, j; + for (i = j = 0; a[i] && b[j] && j < n; i++) { + if (a[i] == b[j]) { + j++; + } else { + j = 0; + } + } + return j == n; +} + +static inline bool compareString(const char *a, const char *b, int blen, int flags) { + const int start = flags & SDB_LIKE_START; + const int end = flags & SDB_LIKE_END; + char *aa = NULL; + int alen; + bool ret = false; + if (!a || !b || blen < 0) { + return 0; + } + if (flags & SDB_LIKE_BASE64) { + aa = (char *)sdb_decode(a, &alen); + if (!aa) { + return 0; + } + a = (const char *)aa; + } else { + alen = strlen(a); + } + if (blen <= alen) { + if (flags & SDB_LIKE_ICASE) { + if (start && end) + ret = (alen == blen && !mycmp(a, b, blen, 0)); + else if (start) + ret = !mycmp(a, b, blen, 0); + else if (end) + ret = !mycmp(a + (alen - blen), b, blen, 0); + else + ret = !mycmp(a, b, blen, 1); + } else { + if (start && end) + ret = (alen == blen && !strncmp(a, b, blen)); + else if (start) + ret = !strncmp(a, b, blen); + else if (end) + ret = !strncmp(a + (alen - blen), b, blen); + else + ret = strstr2(a, b, blen); + } + } + free(aa); + return ret; +} + +RZ_API bool sdb_match(const char *str, const char *glob) { + int glob_len, flags = SDB_LIKE_NONE; + if (!str || !glob) { + return false; + } + glob_len = strlen(glob); + if (haveSuffix(glob, glob_len, "?i")) { + glob_len -= 2; + flags |= SDB_LIKE_ICASE; + } + if (havePrefix(glob, glob_len, "%")) { + glob++; + glob_len--; + flags |= SDB_LIKE_BASE64; + } + if (havePrefix(glob, glob_len, "^")) { + glob++; + glob_len--; + flags |= SDB_LIKE_START; + } + if (haveSuffix(glob, glob_len, "$")) { + glob_len--; + flags |= SDB_LIKE_END; + } + return compareString(str, glob, glob_len, flags); +} diff --git a/librz/util/sdb/src/meson.build b/librz/util/sdb/src/meson.build new file mode 100644 index 00000000000..7dc570e2254 --- /dev/null +++ b/librz/util/sdb/src/meson.build @@ -0,0 +1,51 @@ +libsdb_sources = files( + 'array.c', + 'set.c', + 'base64.c', + 'buffer.c', + 'cdb.c', + 'cdb_make.c', + 'diff.c', + 'disk.c', + 'fmt.c', + 'ht_uu.c', + 'ht_pp.c', + 'ht_up.c', + 'ht_pu.c', + 'journal.c', + 'json.c', + #'json/api.c', + #'json/indent.c', + #'json/js0n.c', + #'json/path.c', + #'json/rangstr.c', + 'lock.c', + 'ls.c', + 'match.c', + 'ns.c', + 'num.c', + 'query.c', + 'sdb.c', + 'sdbht.c', + 'util.c', + 'text.c' +) + +libsdb_inc = [platform_inc, include_directories(['..', '.'])] + +include_files = files( + 'buffer.h', + 'cdb.h', + 'cdb_make.h', + 'ht_inc.h', + 'ht_pp.h', + 'ht_up.h', + 'ht_uu.h', + 'ht_pu.h', + 'ls.h', + 'sdb.h', + 'sdbht.h', + 'set.h', +) + +install_headers(include_files, install_dir: join_paths(rizin_incdir, 'sdb')) diff --git a/librz/util/sdb/src/ns.c b/librz/util/sdb/src/ns.c new file mode 100644 index 00000000000..ff05bd3939e --- /dev/null +++ b/librz/util/sdb/src/ns.c @@ -0,0 +1,253 @@ +// SPDX-FileCopyrightText: 2011-2016 pancake +// SPDX-License-Identifier: MIT + +#include "sdb.h" + +RZ_API void sdb_ns_lock(Sdb *s, int lock, int depth) { + SdbListIter *it; + SdbNs *ns; + s->ns_lock = lock; + if (depth) { // handles -1 as infinite + ls_foreach (s->ns, it, ns) { + sdb_ns_lock(ns->sdb, lock, depth - 1); + } + } +} + +static int in_list(SdbList *list, void *item) { + SdbNs *ns; + SdbListIter *it; + if (list && item) + ls_foreach (list, it, ns) { + if (item == ns) { + return 1; + } + } + return 0; +} + +static void ns_free_exc_list(Sdb *s, SdbList *list) { + SdbListIter next; + SdbListIter *it; + int deleted; + SdbNs *ns; + if (!list || !s) { + return; + } + // TODO: Implement and use ls_foreach_safe + if (in_list(list, s)) { + return; + } + ls_append(list, s); + ls_foreach (s->ns, it, ns) { + deleted = 0; + next.n = it->n; + if (!in_list(list, ns)) { + ls_delete(s->ns, it); // free (it) + free(ns->name); + ns->name = NULL; + deleted = 1; + if (ns->sdb) { + if (sdb_free(ns->sdb)) { + ns->sdb = NULL; + free(ns->name); + ns->name = NULL; + } + } + ls_append(list, ns); + ls_append(list, ns->sdb); + ns_free_exc_list(ns->sdb, list); + sdb_free(ns->sdb); + } + if (!deleted) { + sdb_free(ns->sdb); + s->ns->free = NULL; + ls_delete(s->ns, it); // free (it) + } + free(ns); + it = &next; + } + ls_free(s->ns); + s->ns = NULL; +} + +RZ_API void sdb_ns_free_all(Sdb *s) { + SdbList *list; + if (!s) { + return; + } + list = ls_new(); + list->free = NULL; + ns_free_exc_list(s, list); + ls_free(list); + ls_free(s->ns); + s->ns = NULL; +} + +static SdbNs *sdb_ns_new(Sdb *s, const char *name, ut32 hash) { + char dir[SDB_MAX_PATH]; + SdbNs *ns; + if (s->dir && *s->dir && name && *name) { + int dir_len = strlen(s->dir); + int name_len = strlen(name); + if ((dir_len + name_len + 3) > SDB_MAX_PATH) { + return NULL; + } + memcpy(dir, s->dir, dir_len); + memcpy(dir + dir_len, ".", 1); + memcpy(dir + dir_len + 1, name, name_len + 1); + } else { + dir[0] = 0; + } + ns = malloc(sizeof(SdbNs)); + if (!ns) { + return NULL; + } + ns->hash = hash; + ns->name = name ? strdup(name) : NULL; + // ns->sdb = sdb_new (dir, ns->name, 0); + ns->sdb = sdb_new0(); + // TODO: generate path + + if (ns->sdb) { + free(ns->sdb->path); + ns->sdb->path = NULL; + if (*dir) { + ns->sdb->path = strdup(dir); + } + free(ns->sdb->name); + if (name && *name) { + ns->sdb->name = strdup(name); + } + } else { + free(ns->name); + free(ns); + ns = NULL; + } + return ns; +} + +static void sdb_ns_free(SdbNs *ns) { + sdb_free(ns->sdb); + free(ns->name); + free(ns); +} + +RZ_API bool sdb_ns_unset(Sdb *s, const char *name, Sdb *r) { + SdbNs *ns; + SdbListIter *it; + if (s && (name || r)) { + ls_foreach (s->ns, it, ns) { + if (name && (!strcmp(name, ns->name))) { + sdb_ns_free(ns); + ls_delete(s->ns, it); + return true; + } + if (r && ns->sdb == r) { + sdb_ns_free(ns); + ls_delete(s->ns, it); + return true; + } + } + } + return false; +} + +RZ_API int sdb_ns_set(Sdb *s, const char *name, Sdb *r) { + SdbNs *ns; + SdbListIter *it; + ut32 hash = sdb_hash(name); + if (!s || !r || !name) { + return 0; + } + ls_foreach (s->ns, it, ns) { + if (ns->hash == hash) { + if (ns->sdb == r) { + return 0; + } + sdb_free(ns->sdb); + r->refs++; // sdb_ref / sdb_unref // + ns->sdb = r; + return 1; + } + } + if (s->ns_lock) { + return 0; + } + ns = RZ_NEW(SdbNs); + ns->name = strdup(name); + ns->hash = hash; + ns->sdb = r; + r->refs++; + ls_append(s->ns, ns); + return 1; +} + +RZ_API Sdb *sdb_ns(Sdb *s, const char *name, int create) { + SdbListIter *it; + SdbNs *ns; + ut32 hash; + if (!s || !name || !*name) { + return NULL; + } + hash = sdb_hash(name); + ls_foreach (s->ns, it, ns) { + if (ns->hash == hash) { + return ns->sdb; + } + } + if (!create) { + return NULL; + } + if (s->ns_lock) { + return NULL; + } + ns = sdb_ns_new(s, name, hash); + if (!ns) { + return NULL; + } + ls_append(s->ns, ns); + return ns->sdb; +} + +RZ_API Sdb *sdb_ns_path(Sdb *s, const char *path, int create) { + char *ptr, *str; + char *slash; + + if (!s || !path || !*path) + return s; + ptr = str = strdup(path); + do { + slash = strchr(ptr, '/'); + if (slash) + *slash = 0; + s = sdb_ns(s, ptr, create); + if (!s) + break; + if (slash) + ptr = slash + 1; + } while (slash); + free(str); + return s; +} + +static void ns_sync(Sdb *s, SdbList *list) { + SdbNs *ns; + SdbListIter *it; + ls_foreach (s->ns, it, ns) { + if (in_list(list, ns)) { + continue; + } + ls_append(list, ns); + ns_sync(ns->sdb, list); + sdb_sync(ns->sdb); + } + sdb_sync(s); +} + +RZ_API void sdb_ns_sync(Sdb *s) { + SdbList *list = ls_new(); + ns_sync(s, list); + list->free = NULL; + ls_free(list); +} diff --git a/librz/util/sdb/src/num.c b/librz/util/sdb/src/num.c new file mode 100644 index 00000000000..677ac95eb80 --- /dev/null +++ b/librz/util/sdb/src/num.c @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2011-2016 pancake +// SPDX-License-Identifier: MIT + +#include "sdb.h" +#include + +// check if key exists and if it's a number.. rename? +RZ_API bool sdb_num_exists(Sdb *s, const char *key) { + const char *o = sdb_const_get(s, key, NULL); + return o ? (*o >= '0' && *o <= '9') : false; +} + +RZ_API ut64 sdb_num_get(Sdb *s, const char *key, ut32 *cas) { + const char *v = sdb_const_get(s, key, cas); + return (!v || *v == '-') ? 0LL : sdb_atoi(v); +} + +RZ_API int sdb_num_add(Sdb *s, const char *key, ut64 v, ut32 cas) { + char *val, b[SDB_NUM_BUFSZ]; + int numbase = sdb_num_base(sdb_const_get(s, key, NULL)); + val = sdb_itoa(v, b, numbase); + return sdb_add(s, key, val, cas); +} + +RZ_API int sdb_num_set(Sdb *s, const char *key, ut64 v, ut32 cas) { + char *val, b[SDB_NUM_BUFSZ]; + int numbase = sdb_num_base(sdb_const_get(s, key, NULL)); + val = sdb_itoa(v, b, numbase); + return sdb_set(s, key, val, cas); +} + +RZ_API ut64 sdb_num_inc(Sdb *s, const char *key, ut64 n2, ut32 cas) { + ut32 c; + ut64 n = sdb_num_get(s, key, &c); + ut64 res = n + n2; + if ((cas && c != cas) || res < n) { + return 0LL; + } + sdb_num_set(s, key, res, cas); + return res; +} + +RZ_API ut64 sdb_num_dec(Sdb *s, const char *key, ut64 n2, ut32 cas) { + ut32 c; + ut64 n = sdb_num_get(s, key, &c); + if (cas && c != cas) { + return 0LL; + } + if (n2 > n) { + sdb_set(s, key, "0", cas); + return 0LL; // XXX must be -1LL? + } + n -= n2; + sdb_num_set(s, key, n, cas); + return n; +} + +RZ_API int sdb_num_min(Sdb *db, const char *k, ut64 n, ut32 cas) { + const char *a = sdb_const_get(db, k, NULL); + return (!a || n < sdb_atoi(a)) + ? sdb_num_set(db, k, n, cas) + : 0; +} + +RZ_API int sdb_num_max(Sdb *db, const char *k, ut64 n, ut32 cas) { + const char *a = sdb_const_get(db, k, NULL); + return (!a || n > sdb_atoi(a)) + ? sdb_num_set(db, k, n, cas) + : 0; +} + +RZ_API int sdb_bool_set(Sdb *db, const char *str, bool v, ut32 cas) { + return sdb_set(db, str, v ? "true" : "false", cas); +} + +RZ_API bool sdb_bool_get(Sdb *db, const char *str, ut32 *cas) { + const char *b = sdb_const_get(db, str, cas); + return b && (!strcmp(b, "1") || !strcmp(b, "true")); +} + +/* pointers */ + +RZ_API int sdb_ptr_set(Sdb *db, const char *key, void *p, ut32 cas) { + return sdb_num_set(db, key, (ut64)(size_t)p, cas); +} + +RZ_API void *sdb_ptr_get(Sdb *db, const char *key, ut32 *cas) { + return (void *)(size_t)sdb_num_get(db, key, cas); +} diff --git a/librz/util/sdb/src/query.c b/librz/util/sdb/src/query.c new file mode 100644 index 00000000000..df5b2927257 --- /dev/null +++ b/librz/util/sdb/src/query.c @@ -0,0 +1,921 @@ +// SPDX-FileCopyrightText: 2011-2020 pancake +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include +#include "sdb.h" + +typedef struct { + char *buf; + int len; + int size; +} StrBuf; + +static StrBuf *strbuf_new(void) { + return calloc(sizeof(StrBuf), 1); +} + +#define NEWLINE_AFTER_QUERY 1 + +static StrBuf *strbuf_append(StrBuf *sb, const char *str, const int nl) { + if (!sb || !str || nl < 0) { + return sb; + } + int len = strlen(str); + if ((sb->len + len + 2) >= sb->size) { + int newsize = sb->size + len + 256; + char *b = realloc(sb->buf, newsize); + /// TODO perform free and force all callers to update the ref? + if (!b) { + return NULL; + } + sb->buf = b; + sb->size = newsize; + } + if (sb->buf && str) { + memcpy(sb->buf + sb->len, str, len); + sb->len += len; + } +#if NEWLINE_AFTER_QUERY + if (sb->buf && nl) { + sb->buf[sb->len++] = '\n'; + len++; + } +#endif + if (sb->buf) { + sb->buf[sb->len] = 0; + } + return sb; +} + +static StrBuf *strbuf_free(StrBuf *sb) { + free(sb->buf); + free(sb); + return NULL; +} + +RZ_API int sdb_queryf(Sdb *s, const char *fmt, ...) { + char string[4096]; + int ret; + va_list ap; + va_start(ap, fmt); + vsnprintf(string, sizeof(string), fmt, ap); + ret = sdb_query(s, string); + va_end(ap); + return ret; +} + +RZ_API char *sdb_querysf(Sdb *s, char *buf, size_t buflen, const char *fmt, ...) { + char string[4096]; + char *ret; + va_list ap; + va_start(ap, fmt); + vsnprintf(string, sizeof(string), fmt, ap); + ret = sdb_querys(s, buf, buflen, string); + va_end(ap); + return ret; +} + +// TODO: Reimplement as a function with optimized concat +#define out_concat(x) \ + if (x && *x) { \ + strbuf_append(out, x, 1); \ + } + +typedef struct { + StrBuf *out; + int encode; + char *root; +} ForeachListUser; + +static bool foreach_list_cb(void *user, const char *k, const char *v) { + ForeachListUser *rlu = user; + char *line, *root; + int rlen, klen, vlen; + ut8 *v2 = NULL; + if (!rlu) { + return false; + } + root = rlu->root; + klen = strlen(k); + if (rlu->encode) { + v2 = sdb_decode(v, NULL); + if (v2) { + v = (const char *)v2; + } + } + vlen = strlen(v); + if (root) { + rlen = strlen(root); + line = malloc(klen + vlen + rlen + 3); + if (!line) { + free(v2); + return false; + } + memcpy(line, root, rlen); + line[rlen] = '/'; /*append the '/' at the end of the namespace */ + memcpy(line + rlen + 1, k, klen); + line[rlen + klen + 1] = '='; + memcpy(line + rlen + klen + 2, v, vlen + 1); + } else { + line = malloc(klen + vlen + 2); + if (!line) { + free(v2); + return false; + } + memcpy(line, k, klen); + line[klen] = '='; + memcpy(line + klen + 1, v, vlen + 1); + } + strbuf_append(rlu->out, line, 1); + free(v2); + free(line); + return true; +} + +static void walk_namespace(StrBuf *sb, char *root, int left, char *p, SdbNs *ns, int encode) { + int len; + SdbListIter *it; + char *_out, *out = sb->buf; + SdbNs *n; + ForeachListUser user = { sb, encode, root }; + char *roote = root + strlen(root); + if (!ns->sdb) { + return; + } + /*Pick all key=value in the local ns*/ + sdb_foreach(ns->sdb, foreach_list_cb, &user); + + /*Pick "sub"-ns*/ + ls_foreach (ns->sdb->ns, it, n) { + len = strlen(n->name); + p[0] = '/'; + if (len + 2 < left) { + memcpy(p + 1, n->name, len + 1); + left -= len + 2; + } + _out = out; + walk_namespace(sb, root, left, + roote + len + 1, n, encode); + out = _out; + } +} + +RZ_API char *sdb_querys(Sdb *r, char *buf, size_t len, const char *_cmd) { + int i, d, ok, w, alength, bufset = 0, is_ref = 0, encode = 0; + const char *p, *q, *val = NULL; + char *eq, *tmp, *json, *next, *quot, *slash, *res, + *cmd, *newcmd = NULL, *original_cmd = NULL; + StrBuf *out; + Sdb *s = r; + ut64 n; + if (!s || (!_cmd && !buf)) { + return NULL; + } + out = strbuf_new(); + if ((int)len < 1 || !buf) { + bufset = 1; + buf = malloc((len = 64)); + if (!buf) { + strbuf_free(out); + return NULL; + } + } + if (_cmd) { + cmd = original_cmd = strdup(_cmd); + if (!cmd) { + free(out); + if (bufset) { + free(buf); + } + return NULL; + } + } else { + cmd = buf; + } + // if cmd is null, we take buf as cmd + next = NULL; +repeat: + /* skip spaces */ + while (*cmd && (*cmd == ' ' || *cmd == '\t')) { + cmd++; + } + s = r; + p = cmd; + eq = NULL; + encode = 0; + is_ref = 0; + quot = NULL; + json = NULL; + if (*p == '#') { + p++; + next = strchr(p, ';'); + if (next) { + *next = 0; + } + out_concat(sdb_fmt("0x%08x\n", sdb_hash(p))); + if (next) { + *next = ';'; + } + goto runNext; + } else if (*p == '%') { + encode = 1; + cmd++; + p++; + } + if (next) + *next = ';'; + eq = strchr(p, '='); + if (eq) { + d = 1; + *eq++ = 0; + if (*eq == '$') { + next = strchr(eq + 1, ';'); + if (next) + *next = 0; + val = sdb_const_get(s, eq + 1, 0); + if (!val) { + eprintf("No value for '%s'\n", eq + 1); + goto fail; + } + if (next) + *next = ';'; + is_ref = 1; // protect readonly buffer from being processed + } else { + val = eq; + } + } else { + val = NULL; + d = 0; + } + if (!is_ref) { + next = strchr(val ? val : cmd, ';'); + } + // if (!val) val = eq; + if (!is_ref && val && *val == '"') { + val++; + // TODO: escape \" too + quot = (char *)val; + next_quote: + quot = strchr(quot, '"'); + if (quot) { + if (*(quot - 1) == '\\') { + memmove(quot - 1, quot, strlen(quot) + 1); + goto next_quote; + } + *quot++ = 0; // crash on read only mem!! + } else { + eprintf("Missing quote\n"); + *eq++ = 0; + out = strbuf_free(out); + goto fail; + } + next = strchr(quot, ';'); + } else { + quot = NULL; + } + if (next) { + *next = 0; + } + slash = strchr(cmd, '/'); + while (slash) { + *slash = 0; + s = sdb_ns(s, cmd, eq ? 1 : 0); + if (!s) { + eprintf("Cant find namespace %s\n", cmd); + out = strbuf_free(out); + goto fail; + } + cmd = slash + 1; + slash = strchr(cmd, '/'); + } + if (*cmd == '?') { + const char *val = sdb_const_get(s, cmd + 1, 0); + const char *type = sdb_type(val); + out_concat(type); + } else if (*cmd == '*') { + if (!strcmp(cmd, "***")) { + char root[1024]; // limit namespace length? + SdbListIter *it; + SdbNs *ns; + ls_foreach (s->ns, it, ns) { + int name_len = strlen(ns->name); + if (name_len < (long)sizeof(root)) { + memcpy(root, ns->name, name_len + 1); + walk_namespace(out, root, + sizeof(root) - name_len, + root + name_len, ns, encode); + } else { + eprintf("TODO: Namespace too long\n"); + } + } + goto fail; + } else if (!strcmp(cmd, "**")) { + SdbListIter *it; + SdbNs *ns; + ls_foreach (s->ns, it, ns) { + out_concat(ns->name); + } + goto fail; + } else if (!strcmp(cmd, "*")) { + ForeachListUser user = { out, encode, NULL }; + SdbList *list = sdb_foreach_list(s, true); + SdbListIter *iter; + SdbKv *kv; + ls_foreach (list, iter, kv) { + foreach_list_cb(&user, sdbkv_key(kv), sdbkv_value(kv)); + } + ls_free(list); + goto fail; + } + } + json = strchr(cmd, ':'); + if (*cmd == '[') { + char *tp = strchr(cmd, ']'); + if (!tp) { + eprintf("Missing ']'.\n"); + goto fail; + } + *tp++ = 0; + p = (const char *)tp; + } else { + p = cmd; + } + if (*cmd == '$') { + free(newcmd); + char *nc = sdb_get(s, cmd + 1, 0); + cmd = newcmd = (nc) ? nc : strdup(""); + } + // cmd = val + // cmd is key and val is value + if (*cmd == '.') { + if (s->options & SDB_OPTION_FS) { + if (!sdb_query_file(s, cmd + 1)) { + eprintf("sdb: cannot open '%s'\n", cmd + 1); + goto fail; + } + } else { + eprintf("sdb: filesystem access disabled in config\n"); + } + } else if (*cmd == '~') { // delete + if (cmd[1] == '~') { // grep + SdbKv *kv; + SdbListIter *li; + SdbList *l = sdb_foreach_match(s, cmd + 2, false); + ls_foreach (l, li, kv) { + strbuf_append(out, sdbkv_key(kv), 0); + strbuf_append(out, "=", 0); + strbuf_append(out, sdbkv_value(kv), 1); + } + fflush(stdout); + ls_free(l); + } else { + d = 1; + sdb_unset_like(s, cmd + 1); + } + } else if (*cmd == '+' || *cmd == '-') { + d = 1; + if (!buf) { + buf = calloc(1, len); + if (!buf) { + goto fail; + } + bufset = 1; + } + *buf = 0; + if (cmd[1] == '[') { + const char *eb = strchr(cmd, ']'); + if (!eb) { + eprintf("Missing ']'.\n"); + goto fail; + } + int idx = sdb_atoi(cmd + 2); + /* +[idx]key=n */ + /* -[idx]key=n */ + ut64 curnum = sdb_array_get_num(s, + eb + 1, idx, 0); + if (eq) { + /* +[idx]key=n --> key[idx] += n */ + /* -[idx]key=n --> key[idx] -= n */ + st64 n = sdb_atoi(eq); + if (*cmd == '+') { + curnum += n; + } else if (*cmd == '-') { + curnum -= n; + } else { + // never happens + } + sdb_array_set_num(s, eb + 1, idx, curnum, 0); + } else { + /* +[idx]key --> key[idx] + 1 */ + /* -[idx]key --> key[idx] - 1 */ + char *nstr, numstr[128]; + if (*cmd == '+') { + curnum++; + } else if (*cmd == '-') { + curnum--; + } else { + // never happens + } + nstr = sdb_itoa(curnum, numstr, 10); + strbuf_append(out, nstr, 1); + } + } else if (val) { + if (sdb_isnum(val)) { + int op = *cmd; + if (*val == '-') { + if (*cmd == '-') { + op = '+'; + } else { + op = '-'; + } + d = sdb_atoi(val + 1); + } else { + d = sdb_atoi(val); + } + if (op == '+') { + sdb_num_inc(s, cmd + 1, d, 0); + } else { + sdb_num_dec(s, cmd + 1, d, 0); + } + } else { + if (*cmd == '+') { + sdb_concat(s, cmd + 1, val, 0); + } else { + sdb_uncat(s, cmd + 1, val, 0); + } + } + } else { + int base = sdb_num_base(sdb_const_get(s, cmd + 1, 0)); + if (json) { + base = 10; // NOTE: json is base10 only + *json = 0; + if (*cmd == '+') { + n = sdb_json_num_inc(s, cmd + 1, json + 1, d, 0); + } else { + n = sdb_json_num_dec(s, cmd + 1, json + 1, d, 0); + } + *json = ':'; + } else { + if (*cmd == '+') { + n = sdb_num_inc(s, cmd + 1, d, 0); + } else { + n = sdb_num_dec(s, cmd + 1, d, 0); + } + } + // keep base + if (base == 16) { + w = snprintf(buf, len - 1, "0x%" PFMT64x "x", n); + if (w < 0 || (size_t)w > len) { + if (bufset && len < 0xff) { + free(buf); + buf = malloc(len = 0xff); + if (!buf) { + goto fail; + } + } + bufset = 1; + snprintf(buf, 0xff, "0x%" PFMT64x "x", n); + } + } else { + w = snprintf(buf, len - 1, "%" PFMT64x "d", n); + if (w < 0 || (size_t)w > len) { + if (bufset && len < 0xff) { + free(buf); + buf = malloc(len = 0xff); + if (!buf) { + goto fail; + } + } + bufset = 1; + snprintf(buf, 0xff, "%" PFMT64x "d", n); + } + } + } + out_concat(buf); + } else if (*cmd == '[') { + // [?] - count elements of array + if (cmd[1] == '?') { + // if (!eq) ... + alength = sdb_array_length(s, p); + if (!buf) { + buf = malloc(++len); + if (!buf) { + goto fail; + } + bufset = 1; + } + w = snprintf(buf, len, "%d", alength); + if (w < 0 || (size_t)w > len) { + if (bufset) { + free(buf); + } + buf = malloc(len = 32); + bufset = 1; + snprintf(buf, 31, "%d", alength); + } + out_concat(buf); + } else if (cmd[1] == '!') { + if (cmd[2] == '+') { + // [!+]key=aa # add_sorted + sdb_array_add_sorted(s, p, val, 0); + } else { + // [!]key # sort + sdb_array_sort(s, p, 0); + } + } else if (cmd[1] == '#') { + // [#+]key=num # add_sorted_num + if (cmd[2] == '+') { + // [#]key # sort_num + sdb_array_add_sorted_num(s, p, sdb_atoi(val), 0); + } else { + sdb_array_sort_num(s, p, 0); + } + } else if (cmd[1] == '+' || cmd[1] == '-') { + if (cmd[1] == cmd[2]) { + // stack +#if 0 + [++]foo=33 # push + [++]foo # + [--]foo # pop + [--]foo=b # +#endif + if (cmd[1] == '-' && eq) { + /* invalid syntax */ + } else if (cmd[1] == '+' && !eq) { + /* invalid syntax */ + } else { + if (eq) { + sdb_array_push(s, p, val, 0); + } else { + char *ret = sdb_array_pop(s, p, 0); + out_concat(ret); + free(ret); + } + } + } else + // [+]foo remove first element */ + // [+]foo=bar ADD */ + // [-]foo POP */ + // [-]foo=xx REMOVE (=xx ignored) */ + if (!cmd[2] || cmd[2] == ']') { + // insert + if (eq) { + if (cmd[1] == '+') { + // [+]K=1 + sdb_array_add(s, p, val, 0); + } else { + // [-]K= = remove first element + sdb_array_remove(s, p, val, 0); + } + // return NULL; + } else { + char *ret; + if (cmd[1] == '+') { + // [+]K = remove first element + // XXX: this is a little strange syntax to remove an item + ret = sdb_array_get(s, p, 0, 0); + if (ret && *ret) { + out_concat(ret); + } + // (+)foo :: remove first element + sdb_array_delete(s, p, 0, 0); + } else { + // [-]K = remove last element + ret = sdb_array_get(s, p, -1, 0); + if (ret && *ret) { + out_concat(ret); + } + // (-)foo :: remove last element + sdb_array_delete(s, p, -1, 0); + } + free(ret); + } + } else { + // get/set specific element in array + i = atoi(cmd + 1); + if (eq) { + /* [+3]foo=bla */ + if (i < 0) { + char *tmp = sdb_array_get(s, p, -i, NULL); + if (tmp) { + if (encode) { + char *newtmp = (void *)sdb_decode(tmp, NULL); + if (!newtmp) { + goto fail; + } + free(tmp); + tmp = newtmp; + } + ok = 0; + out_concat(tmp); + sdb_array_delete(s, p, -i, 0); + free(tmp); + } else + goto fail; + } else { + if (encode) { + val = sdb_encode((const ut8 *)val, -1); + } + ok = cmd[1] ? ((cmd[1] == '+') ? sdb_array_insert(s, p, i, val, 0) : sdb_array_set(s, p, i, val, 0)) : sdb_array_delete(s, p, i, 0); + if (encode) { + free((void *)val); + val = NULL; + } + } + if (ok && buf) + *buf = 0; + else + buf = NULL; + } else { + if (i == 0) { + /* [-b]foo */ + if (cmd[1] == '-') { + sdb_array_remove(s, p, cmd + 2, 0); + } else { + eprintf("TODO: [b]foo -> get index of b key inside foo array\n"); + // sdb_array_dels (s, p, cmd+1, 0); + } + } else if (i < 0) { + /* [-3]foo */ + char *tmp = sdb_array_get(s, p, -i, NULL); + if (tmp && *tmp) { + out_concat(tmp); + sdb_array_delete(s, p, -i, 0); + } + free(tmp); + } else { + /* [+3]foo */ + char *tmp = sdb_array_get(s, p, i, NULL); + if (tmp && *tmp) { + out_concat(tmp); + } + free(tmp); + } + } + } + } else { + if (eq) { + /* [3]foo=bla */ + char *sval = (char *)val; + if (encode) { + sval = sdb_encode((const ut8 *)val, -1); + } + if (cmd[1]) { + int idx = atoi(cmd + 1); + ok = sdb_array_set(s, p, idx, sval, 0); + // TODO: handle when idx > sdb_alen + if (encode) + free(sval); + } else { + if (encode) { + ok = sdb_set_owned(s, p, sval, 0); + } else { + ok = sdb_set(s, p, sval, 0); + } + } + if (ok && buf) { + *buf = 0; + } + } else { + /* [3]foo */ + const char *sval = sdb_const_get(s, p, 0); + size_t wl; + if (cmd[1]) { + i = atoi(cmd + 1); + buf = sdb_array_get(s, p, i, NULL); + if (buf) { + bufset = 1; + len = strlen(buf) + 1; + } + if (encode) { + char *newbuf = (void *)sdb_decode(buf, NULL); + if (newbuf) { + free(buf); + buf = newbuf; + len = strlen(buf) + 1; + } + } + out_concat(buf); + } else { + if (!sval) { + goto fail; + } + wl = strlen(sval); + if (!buf || wl >= len) { + buf = malloc(wl + 2); + if (!buf) { + free(out->buf); + out->buf = NULL; + goto fail; + } + bufset = 1; + len = wl + 2; + } + for (i = 0; sval[i]; i++) { + if (sval[i + 1]) { + buf[i] = (sval[i] == SDB_RS) + ? '\n' + : sval[i]; + } else { + buf[i] = sval[i]; + } + } + buf[i] = 0; + if (encode) { + char *newbuf = (void *)sdb_decode(buf, NULL); + if (newbuf) { + if (bufset) { + free(buf); + } + buf = newbuf; + len = strlen(buf) + 1; + } + } + out_concat(buf); + } + } + } + } else { + if (eq) { + // 1 0 kvpath=value + // 1 1 kvpath:jspath=value + if (encode) { + val = sdb_encode((const ut8 *)val, -1); + } + if (json > eq) { + json = NULL; + } + + if (json) { + *json++ = 0; + ok = sdb_json_set(s, cmd, json, val, 0); + } else { + while (*val && isspace(*val)) { + val++; + } + int i = strlen(cmd) - 1; + while (i >= 0 && isspace(cmd[i])) { + cmd[i] = '\0'; + i--; + } + ok = sdb_set(s, cmd, val, 0); + } + if (encode) { + free((void *)val); + val = NULL; + } + if (ok && buf) { + *buf = 0; + } + } else { + // 0 1 kvpath:jspath + // 0 0 kvpath + if (json) { + *json++ = 0; + if (*json) { + // TODO: not optimized to reuse 'buf' + if ((tmp = sdb_json_get(s, cmd, json, 0))) { + if (encode) { + char *newtmp = (void *)sdb_decode(tmp, NULL); + if (!newtmp) + goto fail; + free(tmp); + tmp = newtmp; + } + out_concat(tmp); + free(tmp); + } + } else { + // kvpath: -> show indented json + char *o = sdb_json_indent(sdb_const_get(s, cmd, 0), " "); + out_concat(o); + free(o); + } + } else { + // sdbget + if ((q = sdb_const_get(s, cmd, 0))) { + if (encode) { + q = (void *)sdb_decode(q, NULL); + } + out_concat(q); + if (encode) { + free((void *)q); + } + } + } + } + } +runNext: + if (next) { + if (bufset) { + free(buf); + buf = NULL; + bufset = 0; + } + cmd = next + 1; + encode = 0; + goto repeat; + } + if (eq) { + *--eq = '='; + } +fail: + if (bufset) { + free(buf); + } + if (out) { + res = out->buf; + free(out); + } else { + res = NULL; + } + free(original_cmd); + free(newcmd); + return res; +} + +RZ_API int sdb_query(Sdb *s, const char *cmd) { + char buf[1024], *out; + int must_save = ((*cmd == '~') || strchr(cmd, '=')); + out = sdb_querys(s, buf, sizeof(buf) - 1, cmd); + if (out) { + if (*out) { + puts(out); + } + if (out != buf) { + free(out); + } + } + return must_save; +} + +RZ_API int sdb_query_lines(Sdb *s, const char *cmd) { + char *o, *p, *op; + if (!s || !cmd) { + return 0; + } + op = strdup(cmd); + if (!op) { + return 0; + } + p = op; + do { + o = strchr(p, '\n'); + if (o) { + *o = 0; + } + (void)sdb_query(s, p); + if (o) { + p = o + 1; + } + } while (o); + free(op); + return 1; +} + +static char *slurp(const char *file) { + int ret, fd; + char *text; + long sz; + if (!file || !*file) + return NULL; + fd = open(file, O_RDONLY); + if (fd == -1) { + return NULL; + } + sz = lseek(fd, 0, SEEK_END); + if (sz < 0) { + close(fd); + return NULL; + } + lseek(fd, 0, SEEK_SET); + text = malloc(sz + 1); + if (!text) { + close(fd); + return NULL; + } + ret = read(fd, text, sz); + if (ret != sz) { + free(text); + text = NULL; + } else { + text[sz] = 0; + } + close(fd); + return text; +} + +RZ_API int sdb_query_file(Sdb *s, const char *file) { + int ret = 0; + char *txt = slurp(file); + if (txt) { + ret = sdb_query_lines(s, txt); + free(txt); + } + return ret; +} diff --git a/librz/util/sdb/src/sdb.c b/librz/util/sdb/src/sdb.c new file mode 100644 index 00000000000..8e58b404a18 --- /dev/null +++ b/librz/util/sdb/src/sdb.c @@ -0,0 +1,1179 @@ +// SPDX-FileCopyrightText: 2011-2018 pancake +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include +#include "sdb.h" +#include "sdb_private.h" + +static inline SdbKv *next_kv(HtPP *ht, SdbKv *kv) { + return (SdbKv *)((char *)kv + ht->opt.elem_size); +} + +#define BUCKET_FOREACH(ht, bt, j, kv) \ + for ((j) = 0, (kv) = (SdbKv *)(bt)->arr; j < (bt)->count; (j)++, (kv) = next_kv(ht, kv)) + +#define BUCKET_FOREACH_SAFE(ht, bt, j, count, kv) \ + if ((bt)->arr) \ + for ((j) = 0, (kv) = (SdbKv *)(bt)->arr, (count) = (ht)->count; \ + (j) < (bt)->count; \ + (j) = (count) == (ht)->count ? j + 1 : j, (kv) = (count) == (ht)->count ? next_kv(ht, kv) : kv, (count) = (ht)->count) + +static inline int nextcas(void) { + static ut32 cas = 1; + if (!cas) { + cas++; + } + return cas++; +} + +static SdbHook global_hook = NULL; +static void *global_user = NULL; + +RZ_API void sdb_global_hook(SdbHook hook, void *user) { + global_hook = hook; + global_user = user; +} + +// TODO: use mmap instead of read.. much faster! +RZ_API Sdb *sdb_new0(void) { + return sdb_new(NULL, NULL, 0); +} + +RZ_API Sdb *sdb_new(const char *path, const char *name, int lock) { + Sdb *s = RZ_NEW0(Sdb); + if (!s) { + return NULL; + } + s->db.fd = -1; + s->fd = -1; + s->refs = 1; + if (path && !*path) { + path = NULL; + } + if (name && *name && strcmp(name, "-")) { + if (path && *path) { + int plen = strlen(path); + int nlen = strlen(name); + s->dir = malloc(plen + nlen + 2); + if (!s->dir) { + free(s); + return NULL; + } + memcpy(s->dir, path, plen); + s->dir[plen] = '/'; + memcpy(s->dir + plen + 1, name, nlen + 1); + s->path = strdup(path); + } else { + s->dir = strdup(name); + } + switch (lock) { + case 1: + if (!sdb_lock(sdb_lock_file(s->dir))) { + goto fail; + } + break; + case 2: + if (!sdb_lock_wait(sdb_lock_file(s->dir))) { + goto fail; + } + break; + } + if (sdb_open(s, s->dir) == -1) { + s->last = s->timestamped ? sdb_now() : 0LL; + // TODO: must fail if we cant open for write in sync + } + s->name = strdup(name); + } else { + s->last = s->timestamped ? sdb_now() : 0LL; + s->fd = -1; + } + s->journal = -1; + s->fdump = -1; + s->depth = 0; + s->ndump = NULL; + s->ns = ls_new(); // TODO: should be NULL + if (!s->ns) { + goto fail; + } + s->ns->free = NULL; + if (!s->ns) { + goto fail; + } + s->ht = sdb_ht_new(); + s->lock = lock; + // if open fails ignore + if (global_hook) { + sdb_hook(s, global_hook, global_user); + } + cdb_init(&s->db, s->fd); + return s; +fail: + if (s->fd != -1) { + close(s->fd); + s->fd = -1; + } + free(s->dir); + free(s->name); + free(s->path); + free(s); + return NULL; +} + +// XXX: this is wrong. stuff not stored in memory is lost +RZ_API void sdb_file(Sdb *s, const char *dir) { + if (s->lock) { + sdb_unlock(sdb_lock_file(s->dir)); + } + free(s->dir); + s->dir = (dir && *dir) ? strdup(dir) : NULL; + if (s->lock) { + sdb_lock(sdb_lock_file(s->dir)); + } +} + +static bool sdb_merge_cb(void *user, const char *k, const char *v) { + sdb_set(user, k, v, 0); + return true; +} + +RZ_API bool sdb_merge(Sdb *d, Sdb *s) { + return sdb_foreach(s, sdb_merge_cb, d); +} + +RZ_API bool sdb_isempty(Sdb *s) { + if (s) { + if (s->db.fd != -1) { + sdb_dump_begin(s); + while (sdb_dump_hasnext(s)) { + return false; + } + } + if (s->ht && s->ht->count > 0) { + return false; + } + } + return true; +} + +RZ_API int sdb_count(Sdb *s) { + int count = 0; + if (s) { + if (s->db.fd != -1) { + sdb_dump_begin(s); + while (sdb_dump_hasnext(s)) { + count++; + } + } + if (s->ht) { + count += s->ht->count; + } + } + return count; +} + +static void sdb_fini(Sdb *s, int donull) { + if (!s) { + return; + } + sdb_hook_free(s); + cdb_free(&s->db); + if (s->lock) { + sdb_unlock(sdb_lock_file(s->dir)); + } + sdb_ns_free_all(s); + s->refs = 0; + free(s->name); + free(s->path); + ls_free(s->ns); + sdb_ht_free(s->ht); + sdb_journal_close(s); + if (s->fd != -1) { + close(s->fd); + s->fd = -1; + } + free(s->ndump); + free(s->dir); + if (donull) { + memset(s, 0, sizeof(Sdb)); + } +} + +RZ_API bool sdb_free(Sdb *s) { + if (s && s->ht && s->refs) { + s->refs--; + if (s->refs < 1) { + s->refs = 0; + sdb_fini(s, 0); + s->ht = NULL; + free(s); + return true; + } + } + return false; +} + +RZ_API const char *sdb_const_get_len(Sdb *s, const char *key, int *vlen, ut32 *cas) { + ut32 pos, len; + ut64 now = 0LL; + bool found; + + if (cas) { + *cas = 0; + } + if (vlen) { + *vlen = 0; + } + if (!s || !key) { + return NULL; + } + // TODO: optimize, iterate once + size_t keylen = strlen(key); + + /* search in memory */ + SdbKv *kv = (SdbKv *)sdb_ht_find_kvp(s->ht, key, &found); + if (found) { + if (!sdbkv_value(kv) || !*sdbkv_value(kv)) { + return NULL; + } + if (s->timestamped && kv->expire) { + if (!now) { + now = sdb_now(); + } + if (now > kv->expire) { + sdb_unset(s, key, 0); + return NULL; + } + } + if (cas) { + *cas = kv->cas; + } + if (vlen) { + *vlen = sdbkv_value_len(kv); + } + return sdbkv_value(kv); + } + /* search in disk */ + if (s->fd == -1) { + return NULL; + } + (void)cdb_findstart(&s->db); + if (cdb_findnext(&s->db, s->ht->opt.hashfn(key), key, keylen) < 1) { + return NULL; + } + len = cdb_datalen(&s->db); + if (len < SDB_CDB_MIN_VALUE || len >= SDB_CDB_MAX_VALUE) { + return NULL; + } + if (vlen) { + *vlen = len; + } + pos = cdb_datapos(&s->db); + return s->db.map + pos; +} + +RZ_API const char *sdb_const_get(Sdb *s, const char *key, ut32 *cas) { + return sdb_const_get_len(s, key, NULL, cas); +} + +// TODO: add sdb_getf? + +RZ_API char *sdb_get_len(Sdb *s, const char *key, int *vlen, ut32 *cas) { + const char *value = sdb_const_get_len(s, key, vlen, cas); + return value ? strdup(value) : NULL; +} + +RZ_API char *sdb_get(Sdb *s, const char *key, ut32 *cas) { + return sdb_get_len(s, key, NULL, cas); +} + +RZ_API int sdb_unset(Sdb *s, const char *key, ut32 cas) { + return key ? sdb_set(s, key, "", cas) : 0; +} + +/* remove from memory */ +RZ_API bool sdb_remove(Sdb *s, const char *key, ut32 cas) { + return sdb_ht_delete(s->ht, key); +} + +// alias for '-key=str'.. '+key=str' concats +RZ_API int sdb_uncat(Sdb *s, const char *key, const char *value, ut32 cas) { + // remove 'value' from current key value. + // TODO: cas is ignored here + int vlen = 0, valen; + char *p, *v = sdb_get_len(s, key, &vlen, NULL); + int mod = 0; + if (!v || !key || !value) { + free(v); + return 0; + } + valen = strlen(value); + if (valen > 0) { + while ((p = strstr(v, value))) { + memmove(p, p + valen, strlen(p + valen) + 1); + mod = 1; + } + } + if (mod) { + sdb_set_owned(s, key, v, 0); + } else { + free(v); + } + return 0; +} + +RZ_API int sdb_concat(Sdb *s, const char *key, const char *value, ut32 cas) { + int kl, vl; + const char *p; + char *o; + if (!s || !key || !*key || !value || !*value) { + return 0; + } + p = sdb_const_get_len(s, key, &kl, 0); + if (!p) { + return sdb_set(s, key, value, cas); + } + vl = strlen(value); + o = malloc(kl + vl + 1); + if (o) { + memcpy(o, p, kl); + memcpy(o + kl, value, vl + 1); + return sdb_set_owned(s, key, o, cas); + } + return 0; +} + +// set if not defined +RZ_API int sdb_add(Sdb *s, const char *key, const char *val, ut32 cas) { + if (sdb_exists(s, key)) { + return 0; + } + return sdb_set(s, key, val, cas); +} + +RZ_API bool sdb_exists(Sdb *s, const char *key) { + ut32 pos; + char ch; + bool found; + int klen = strlen(key) + 1; + if (!s) { + return false; + } + SdbKv *kv = (SdbKv *)sdb_ht_find_kvp(s->ht, key, &found); + if (found && kv) { + char *v = sdbkv_value(kv); + return v && *v; + } + if (s->fd == -1) { + return false; + } + (void)cdb_findstart(&s->db); + if (cdb_findnext(&s->db, sdb_hash(key), key, klen)) { + pos = cdb_datapos(&s->db); + cdb_read(&s->db, &ch, 1, pos); + return ch != 0; + } + return false; +} + +RZ_API int sdb_open(Sdb *s, const char *file) { + struct stat st; + if (!s) { + return -1; + } + if (file) { + if (s->fd != -1) { + close(s->fd); + s->fd = -1; + } + s->fd = open(file, O_RDONLY | O_BINARY); + if (file != s->dir) { + free(s->dir); + s->dir = strdup(file); + s->path = NULL; // TODO: path is important + } + } + s->last = 0LL; + if (s->fd != -1 && fstat(s->fd, &st) != -1) { + if ((S_IFREG & st.st_mode) != S_IFREG) { + eprintf("Database must be a file\n"); + close(s->fd); + s->fd = -1; + return -1; + } + s->last = st.st_mtime; + } + if (s->fd != -1) { + cdb_init(&s->db, s->fd); + } + return s->fd; +} + +RZ_API void sdb_close(Sdb *s) { + if (s) { + if (s->fd != -1) { + if (s->db.fd != -1 && s->db.fd == s->fd) { + /* close db fd as well */ + s->db.fd = -1; + } + close(s->fd); + s->fd = -1; + } + if (s->dir) { + free(s->dir); + s->dir = NULL; + } + } +} + +RZ_API void sdb_reset(Sdb *s) { + if (!s) { + return; + } + /* ignore disk cache, file is not removed, but we will ignore + * its values when syncing again */ + sdb_close(s); + /* empty memory hashtable */ + sdb_ht_free(s->ht); + s->ht = sdb_ht_new(); +} + +static char lastChar(const char *str) { + int len = strlen(str); + return str[(len > 0) ? len - 1 : 0]; +} + +static bool match(const char *str, const char *expr) { + bool startsWith = *expr == '^'; + bool endsWith = lastChar(expr) == '$'; + if (startsWith && endsWith) { + return strlen(str) == strlen(expr) - 2 && + !strncmp(str, expr + 1, strlen(expr) - 2); + } + if (startsWith) { + return !strncmp(str, expr + 1, strlen(expr) - 1); + } + if (endsWith) { + int alen = strlen(str); + int blen = strlen(expr) - 1; + if (alen <= blen) { + return false; + } + const char *a = str + strlen(str) - blen; + return (!strncmp(a, expr, blen)); + } + return strstr(str, expr); +} + +RZ_API bool sdbkv_match(SdbKv *kv, const char *expr) { + // TODO: add syntax to negate condition + // TODO: add syntax to OR k/v instead of AND + // [^]str[$]=[^]str[$] + const char *eq = strchr(expr, '='); + if (eq) { + char *e = strdup(expr); + char *ep = e + (eq - expr); + *ep++ = 0; + bool res = !*e || match(sdbkv_key(kv), e); + bool res2 = !*ep || match(sdbkv_value(kv), ep); + free(e); + return res && res2; + } + return match(sdbkv_key(kv), expr); +} + +RZ_API SdbKv *sdbkv_new(const char *k, const char *v) { + return sdbkv_new2(k, strlen(k), v, strlen(v)); +} + +RZ_API SdbKv *sdbkv_new2(const char *k, int kl, const char *v, int vl) { + SdbKv *kv; + if (!v) { + vl = 0; + } + kv = RZ_NEW0(SdbKv); + kv->base.key_len = kl; + kv->base.key = malloc(kv->base.key_len + 1); + if (!kv->base.key) { + free(kv); + return NULL; + } + memcpy(kv->base.key, k, kv->base.key_len + 1); + kv->base.value_len = vl; + if (vl) { + kv->base.value = malloc(vl + 1); + if (!kv->base.value) { + free(kv->base.key); + free(kv); + return NULL; + } + memcpy(kv->base.value, v, vl + 1); + } else { + kv->base.value = NULL; + kv->base.value_len = 0; + } + kv->cas = nextcas(); + kv->expire = 0LL; + return kv; +} + +RZ_API void sdbkv_free(SdbKv *kv) { + if (kv) { + free(sdbkv_key(kv)); + free(sdbkv_value(kv)); + RZ_FREE(kv); + } +} + +static ut32 sdb_set_internal(Sdb *s, const char *key, char *val, int owned, ut32 cas) { + ut32 vlen, klen; + SdbKv *kv; + bool found; + if (!s || !key) { + return 0; + } + if (!val) { + if (owned) { + val = strdup(""); + } else { + val = ""; + } + } + // XXX strlen computed twice.. because of check_*() + klen = strlen(key); + vlen = strlen(val); + if (s->journal != -1) { + sdb_journal_log(s, key, val); + } + cdb_findstart(&s->db); + kv = sdb_ht_find_kvp(s->ht, key, &found); + if (found && sdbkv_value(kv)) { + if (cdb_findnext(&s->db, sdb_hash(key), key, klen)) { + if (cas && kv->cas != cas) { + if (owned) { + free(val); + } + return 0; + } + if (vlen == sdbkv_value_len(kv) && !strcmp(sdbkv_value(kv), val)) { + sdb_hook_call(s, key, val); + return kv->cas; + } + kv->cas = cas = nextcas(); + if (owned) { + kv->base.value_len = vlen; + free(kv->base.value); + kv->base.value = val; // owned + } else { + if ((ut32)vlen > kv->base.value_len) { + free(kv->base.value); + kv->base.value = malloc(vlen + 1); + } + memcpy(kv->base.value, val, vlen + 1); + kv->base.value_len = vlen; + } + } else { + sdb_ht_delete(s->ht, key); + } + sdb_hook_call(s, key, val); + return cas; + } + // empty values are also stored + // TODO store only the ones that are in the CDB + if (owned) { + kv = sdbkv_new2(key, klen, NULL, 0); + if (kv) { + kv->base.value = val; + kv->base.value_len = vlen; + } + } else { + kv = sdbkv_new2(key, klen, val, vlen); + } + if (kv) { + ut32 cas = kv->cas = nextcas(); + sdb_ht_insert_kvp(s->ht, kv, true /*update*/); + free(kv); + sdb_hook_call(s, key, val); + return cas; + } + // kv set failed, no need to callback sdb_hook_call (s, key, val); + return 0; +} + +RZ_API int sdb_set_owned(Sdb *s, const char *key, char *val, ut32 cas) { + return sdb_set_internal(s, key, val, 1, cas); +} + +RZ_API int sdb_set(Sdb *s, const char *key, const char *val, ut32 cas) { + return sdb_set_internal(s, key, (char *)val, 0, cas); +} + +static bool sdb_foreach_list_cb(void *user, const char *k, const char *v) { + SdbList *list = (SdbList *)user; + SdbKv *kv = RZ_NEW0(SdbKv); + /* seems like some k/v are constructed in the stack and cant be used after returning */ + kv->base.key = strdup(k); + kv->base.value = strdup(v); + ls_append(list, kv); + return 1; +} + +static int __cmp_asc(const void *a, const void *b) { + const SdbKv *ka = a, *kb = b; + return strcmp(sdbkv_key(ka), sdbkv_key(kb)); +} + +RZ_API SdbList *sdb_foreach_list(Sdb *s, bool sorted) { + SdbList *list = ls_newf((SdbListFree)sdbkv_free); + sdb_foreach(s, sdb_foreach_list_cb, list); + if (sorted) { + ls_sort(list, __cmp_asc); + } + return list; +} + +struct foreach_list_filter_t { + SdbForeachCallback filter; + SdbList *list; + void *user; +}; + +static bool sdb_foreach_list_filter_cb(void *user, const char *k, const char *v) { + struct foreach_list_filter_t *u = (struct foreach_list_filter_t *)user; + SdbList *list = u->list; + SdbKv *kv = NULL; + + if (!u->filter || u->filter(u->user, k, v)) { + kv = RZ_NEW0(SdbKv); + if (!kv) { + goto err; + } + kv->base.key = strdup(k); + kv->base.value = strdup(v); + if (!kv->base.key || !kv->base.value) { + goto err; + } + ls_append(list, kv); + } + return true; +err: + sdbkv_free(kv); + return false; +} + +RZ_API SdbList *sdb_foreach_list_filter_user(Sdb *s, SdbForeachCallback filter, bool sorted, void *user) { + struct foreach_list_filter_t u; + SdbList *list = ls_newf((SdbListFree)sdbkv_free); + + if (!list) { + return NULL; + } + u.filter = filter; + u.list = list; + u.user = user; + sdb_foreach(s, sdb_foreach_list_filter_cb, &u); + if (sorted) { + ls_sort(list, __cmp_asc); + } + return list; +} + +RZ_API SdbList *sdb_foreach_list_filter(Sdb *s, SdbForeachCallback filter, bool sorted) { + return sdb_foreach_list_filter_user(s, filter, sorted, NULL); +} + +typedef struct { + const char *expr; + SdbList *list; + bool single; +} _match_sdb_user; + +static bool sdb_foreach_match_cb(void *user, const char *k, const char *v) { + _match_sdb_user *o = (_match_sdb_user *)user; + SdbKv tkv = { .base.key = (char *)k, .base.value = (char *)v }; + if (sdbkv_match(&tkv, o->expr)) { + SdbKv *kv = RZ_NEW0(SdbKv); + kv->base.key = strdup(k); + kv->base.value = strdup(v); + ls_append(o->list, kv); + if (o->single) { + return false; + } + } + return true; +} + +RZ_API SdbList *sdb_foreach_match(Sdb *s, const char *expr, bool single) { + SdbList *list = ls_newf((SdbListFree)sdbkv_free); + _match_sdb_user o = { expr, list, single }; + sdb_foreach(s, sdb_foreach_match_cb, &o); + return list; +} + +static int getbytes(Sdb *s, char *b, int len) { + if (!cdb_read(&s->db, b, len, s->pos)) { + return -1; + } + s->pos += len; + return len; +} + +static bool sdb_foreach_end(Sdb *s, bool result) { + s->depth--; + return result; +} + +static bool sdb_foreach_cdb(Sdb *s, SdbForeachCallback cb, SdbForeachCallback cb2, void *user) { + char *v = NULL; + char k[SDB_CDB_MAX_KEY] = { 0 }; + bool found; + sdb_dump_begin(s); + while (sdb_dump_dupnext(s, k, &v, NULL)) { + SdbKv *kv = sdb_ht_find_kvp(s->ht, k, &found); + if (found) { + free(v); + if (kv && sdbkv_key(kv) && sdbkv_value(kv)) { + if (!cb(user, sdbkv_key(kv), sdbkv_value(kv))) { + return false; + } + if (cb2 && !cb2(user, k, sdbkv_value(kv))) { + return false; + } + } + } else { + if (!cb(user, k, v)) { + free(v); + return false; + } + free(v); + } + } + return true; +} + +RZ_API bool sdb_foreach(Sdb *s, SdbForeachCallback cb, void *user) { + if (!s) { + return false; + } + s->depth++; + bool result = sdb_foreach_cdb(s, cb, NULL, user); + if (!result) { + return sdb_foreach_end(s, false); + } + + ut32 i; + for (i = 0; i < s->ht->size; ++i) { + HtPPBucket *bt = &s->ht->table[i]; + SdbKv *kv; + ut32 j, count; + + BUCKET_FOREACH_SAFE(s->ht, bt, j, count, kv) { + if (kv && sdbkv_value(kv) && *sdbkv_value(kv)) { + if (!cb(user, sdbkv_key(kv), sdbkv_value(kv))) { + return sdb_foreach_end(s, false); + } + } + } + } + return sdb_foreach_end(s, true); +} + +static bool _insert_into_disk(void *user, const char *key, const char *value) { + Sdb *s = (Sdb *)user; + if (s) { + sdb_disk_insert(s, key, value); + return true; + } + return false; +} + +static bool _remove_afer_insert(void *user, const char *k, const char *v) { + Sdb *s = (Sdb *)user; + if (s) { + sdb_ht_delete(s->ht, k); + return true; + } + return false; +} + +RZ_API bool sdb_sync(Sdb *s) { + bool result; + ut32 i; + + if (!s || !sdb_disk_create(s)) { + return false; + } + result = sdb_foreach_cdb(s, _insert_into_disk, _remove_afer_insert, s); + if (!result) { + return false; + } + + /* append new keyvalues */ + for (i = 0; i < s->ht->size; ++i) { + HtPPBucket *bt = &s->ht->table[i]; + SdbKv *kv; + ut32 j, count; + + BUCKET_FOREACH_SAFE(s->ht, bt, j, count, kv) { + if (sdbkv_key(kv) && sdbkv_value(kv) && *sdbkv_value(kv) && !kv->expire) { + if (sdb_disk_insert(s, sdbkv_key(kv), sdbkv_value(kv))) { + sdb_remove(s, sdbkv_key(kv), 0); + } + } + } + } + sdb_disk_finish(s); + sdb_journal_clear(s); + // TODO: sdb_reset memory state? + return true; +} + +RZ_API void sdb_dump_begin(Sdb *s) { + if (s->fd != -1) { + s->pos = sizeof(((struct cdb_make *)0)->final); + seek_set(s->fd, s->pos); + } else { + s->pos = 0; + } +} + +RZ_API bool sdb_dump_hasnext(Sdb *s) { + ut32 k, v; + if (!cdb_getkvlen(&s->db, &k, &v, s->pos)) { + return false; + } + if (k < 1 || v < 1) { + return false; + } + s->pos += k + v + 4; + return true; +} + +RZ_API bool sdb_stats(Sdb *s, ut32 *disk, ut32 *mem) { + if (!s) { + return false; + } + if (disk) { + ut32 count = 0; + if (s->fd != -1) { + sdb_dump_begin(s); + while (sdb_dump_hasnext(s)) { + count++; + } + } + *disk = count; + } + if (mem) { + *mem = s->ht->count; + } + return disk || mem; +} + +// TODO: make it static? internal api? +RZ_API bool sdb_dump_dupnext(Sdb *s, char *key, char **value, int *_vlen) { + ut32 vlen = 0, klen = 0; + if (value) { + *value = NULL; + } + if (_vlen) { + *_vlen = 0; + } + if (!cdb_getkvlen(&s->db, &klen, &vlen, s->pos)) { + return false; + } + s->pos += 4; + if (klen < 1 || vlen < 1) { + return false; + } + if (_vlen) { + *_vlen = vlen; + } + if (key) { + key[0] = 0; + if (klen > SDB_CDB_MIN_KEY && klen < SDB_CDB_MAX_KEY) { + if (getbytes(s, key, klen) == -1) { + return 0; + } + key[klen] = 0; + } + } + if (value) { + *value = 0; + if (vlen < SDB_CDB_MAX_VALUE) { + *value = malloc(vlen + 10); + if (!*value) { + return false; + } + if (getbytes(s, *value, vlen) == -1) { + free(*value); + *value = NULL; + return false; + } + (*value)[vlen] = 0; + } + } + return true; +} + +static inline ut64 parse_expire(ut64 e) { + const ut64 month = 30 * 24 * 60 * 60; + if (e > 0 && e < month) { + e += sdb_now(); + } + return e; +} + +RZ_API bool sdb_expire_set(Sdb *s, const char *key, ut64 expire, ut32 cas) { + char *buf; + ut32 pos, len; + SdbKv *kv; + bool found; + s->timestamped = true; + if (!key) { + s->expire = parse_expire(expire); + return true; + } + kv = (SdbKv *)sdb_ht_find_kvp(s->ht, key, &found); + if (found && kv) { + if (*sdbkv_value(kv)) { + if (!cas || cas == kv->cas) { + kv->expire = parse_expire(expire); + return true; + } + } + return false; + } + if (s->fd == -1) { + return false; + } + (void)cdb_findstart(&s->db); + if (!cdb_findnext(&s->db, sdb_hash(key), key, strlen(key) + 1)) { + return false; + } + pos = cdb_datapos(&s->db); + len = cdb_datalen(&s->db); + if (len < 1 || len >= INT32_MAX) { + return false; + } + if (!(buf = calloc(1, len + 1))) { + return false; + } + cdb_read(&s->db, buf, len, pos); + buf[len] = 0; + sdb_set_owned(s, key, buf, cas); + return sdb_expire_set(s, key, expire, cas); // recursive +} + +RZ_API ut64 sdb_expire_get(Sdb *s, const char *key, ut32 *cas) { + bool found = false; + SdbKv *kv = (SdbKv *)sdb_ht_find_kvp(s->ht, key, &found); + if (found && kv && *sdbkv_value(kv)) { + if (cas) { + *cas = kv->cas; + } + return kv->expire; + } + return 0LL; +} + +RZ_API bool sdb_hook(Sdb *s, SdbHook cb, void *user) { + int i = 0; + SdbHook hook; + SdbListIter *iter; + if (s->hooks) { + ls_foreach (s->hooks, iter, hook) { + if (!(i % 2) && (hook == cb)) { + return false; + } + i++; + } + } else { + s->hooks = ls_new(); + s->hooks->free = NULL; + } + ls_append(s->hooks, (void *)cb); + ls_append(s->hooks, user); + return true; +} + +RZ_API bool sdb_unhook(Sdb *s, SdbHook h) { + int i = 0; + SdbHook hook; + SdbListIter *iter, *iter2; + ls_foreach (s->hooks, iter, hook) { + if (!(i % 2) && (hook == h)) { + iter2 = iter->n; + ls_delete(s->hooks, iter); + ls_delete(s->hooks, iter2); + return true; + } + i++; + } + return false; +} + +RZ_API int sdb_hook_call(Sdb *s, const char *k, const char *v) { + SdbListIter *iter; + SdbHook hook; + int i = 0; + if (s->timestamped && s->last) { + s->last = sdb_now(); + } + ls_foreach (s->hooks, iter, hook) { + if (!(i % 2) && k && iter->n) { + void *u = iter->n->data; + hook(s, u, k, v); + } + i++; + } + return i >> 1; +} + +RZ_API void sdb_hook_free(Sdb *s) { + ls_free(s->hooks); + s->hooks = NULL; +} + +RZ_API void sdb_config(Sdb *s, int options) { + s->options = options; + if (options & SDB_OPTION_SYNC) { + // sync on every query + } + if (options & SDB_OPTION_JOURNAL) { + // sync on every query + sdb_journal_open(s); + // load journaling if exists + sdb_journal_load(s); + sdb_journal_clear(s); + } else { + sdb_journal_close(s); + } + if (options & SDB_OPTION_NOSTAMP) { + // sync on every query + s->last = 0LL; + } + if (options & SDB_OPTION_FS) { + // have access to fs (handle '.' or not in query) + } +} + +RZ_API bool sdb_unlink(Sdb *s) { + sdb_fini(s, 1); + return sdb_disk_unlink(s); +} + +RZ_API void sdb_drain(Sdb *s, Sdb *f) { + if (s && f) { + f->refs = s->refs; + sdb_fini(s, 1); + *s = *f; + free(f); + } +} + +static bool copy_foreach_cb(void *user, const char *k, const char *v) { + Sdb *dst = user; + sdb_set(dst, k, v, 0); + return true; +} + +RZ_API void sdb_copy(Sdb *src, Sdb *dst) { + sdb_foreach(src, copy_foreach_cb, dst); + SdbListIter *it; + SdbNs *ns; + ls_foreach (src->ns, it, ns) { + sdb_copy(ns->sdb, sdb_ns(dst, ns->name, true)); + } +} + +typedef struct { + Sdb *sdb; + const char *key; +} UnsetCallbackData; + +static bool unset_cb(void *user, const char *k, const char *v) { + UnsetCallbackData *ucd = user; + if (sdb_match(k, ucd->key)) { + sdb_unset(ucd->sdb, k, 0); + } + return true; +} + +RZ_API int sdb_unset_like(Sdb *s, const char *k) { + UnsetCallbackData ucd = { s, k }; + return sdb_foreach(s, unset_cb, &ucd); +} + +typedef struct { + Sdb *sdb; + const char *key; + const char *val; + SdbForeachCallback cb; + const char **array; + int array_index; + int array_size; +} LikeCallbackData; + +static bool like_cb(void *user, const char *k, const char *v) { + LikeCallbackData *lcd = user; + if (!user) { + return false; + } + if (k && lcd->key && !sdb_match(k, lcd->key)) { + return true; + } + if (v && lcd->val && !sdb_match(v, lcd->val)) { + return true; + } + if (lcd->array) { + int idx = lcd->array_index; + int newsize = lcd->array_size + sizeof(char *) * 2; + const char **newarray = (const char **)realloc((void *)lcd->array, newsize); + if (!newarray) { + return false; + } + lcd->array = newarray; + lcd->array_size = newsize; + // concatenate in array + lcd->array[idx] = k; + lcd->array[idx + 1] = v; + lcd->array[idx + 2] = NULL; + lcd->array[idx + 3] = NULL; + lcd->array_index = idx + 2; + } else { + if (lcd->cb) { + lcd->cb(lcd->sdb, k, v); + } + } + return true; +} + +RZ_API char **sdb_like(Sdb *s, const char *k, const char *v, SdbForeachCallback cb) { + LikeCallbackData lcd = { s, k, v, cb, NULL, 0, 0 }; + if (cb) { + sdb_foreach(s, like_cb, &lcd); + return NULL; + } + if (k && !*k) { + lcd.key = NULL; + } + if (v && !*v) { + lcd.val = NULL; + } + lcd.array_size = sizeof(char *) * 2; + lcd.array = calloc(lcd.array_size, 1); + if (!lcd.array) { + return NULL; + } + lcd.array_index = 0; + sdb_foreach(s, like_cb, &lcd); + if (lcd.array_index == 0) { + free((void *)lcd.array); + return NULL; + } + return (char **)lcd.array; +} diff --git a/librz/util/sdb/src/sdb.h b/librz/util/sdb/src/sdb.h new file mode 100644 index 00000000000..6dbde37c7a8 --- /dev/null +++ b/librz/util/sdb/src/sdb.h @@ -0,0 +1,401 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#ifndef SDB_H +#define SDB_H + +#if !defined(O_BINARY) && !defined(_MSC_VER) +#undef O_BINARY +#define O_BINARY 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "sdbht.h" +#include "ls.h" +#include "cdb.h" +#include "cdb_make.h" + +/* Key value sizes */ +#define SDB_CDB_MIN_VALUE 1 +#define SDB_CDB_MAX_VALUE CDB_MAX_VALUE +#define SDB_CDB_MIN_KEY 1 +#define SDB_CDB_MAX_KEY CDB_MAX_KEY + +#if !defined(SZT_ADD_OVFCHK) +#define SZT_ADD_OVFCHK(x, y) ((SIZE_MAX - (x)) <= (y)) +#endif + +/* printf format check attributes */ +#if defined(__clang__) || defined(__GNUC__) +#define SDB_PRINTF_CHECK(fmt, dots) __attribute__((format(printf, fmt, dots))) +#else +#define SDB_PRINTF_CHECK(fmt, dots) +#endif + +#if __WINDOWS__ && !__CYGWIN__ +#include +#include +#include +#include +#include +#define ftruncate _chsize +#ifndef _MSC_VER +extern __attribute__((dllimport)) void *__cdecl _aligned_malloc(size_t, size_t); +extern __attribute__((dllimport)) void __cdecl _aligned_free(void *memblock); +extern char *strdup(const char *); +#else +#include +#include // for _aligned_malloc +#endif + +//#define SDB_MODE 0 +#define SDB_MODE _S_IWRITE | _S_IREAD +#else +#define SDB_MODE 0644 +//#define SDB_MODE 0600 +#endif + +//#define SDB_RS '\x1e' +#define SDB_RS ',' +#define SDB_SS "," +#define SDB_MAX_PATH 256 +#define SDB_NUM_BASE 16 +#define SDB_NUM_BUFSZ 64 + +#define SDB_OPTION_NONE 0 +#define SDB_OPTION_ALL 0xff +#define SDB_OPTION_SYNC (1 << 0) +#define SDB_OPTION_NOSTAMP (1 << 1) +#define SDB_OPTION_FS (1 << 2) +#define SDB_OPTION_JOURNAL (1 << 3) + +#define SDB_LIST_UNSORTED 0 +#define SDB_LIST_SORTED 1 + +typedef struct sdb_t { + char *dir; // path+name + char *path; + char *name; + int fd; + int refs; // reference counter + int lock; + int journal; + struct cdb db; + struct cdb_make m; + HtPP *ht; + ut32 eod; + ut32 pos; + int fdump; + char *ndump; + ut64 expire; + ut64 last; // timestamp of last change + int options; + int ns_lock; // TODO: merge into options? + SdbList *ns; + SdbList *hooks; + ut32 depth; + bool timestamped; +} Sdb; + +typedef struct sdb_ns_t { + char *name; + ut32 hash; + Sdb *sdb; +} SdbNs; + +RZ_API Sdb *sdb_new0(void); +RZ_API Sdb *sdb_new(const char *path, const char *file, int lock); + +RZ_API int sdb_open(Sdb *s, const char *file); +RZ_API void sdb_close(Sdb *s); + +RZ_API void sdb_config(Sdb *s, int options); +RZ_API bool sdb_free(Sdb *s); +RZ_API void sdb_file(Sdb *s, const char *dir); +RZ_API bool sdb_merge(Sdb *d, Sdb *s); +RZ_API int sdb_count(Sdb *s); +RZ_API void sdb_reset(Sdb *s); +RZ_API void sdb_setup(Sdb *s, int options); +RZ_API void sdb_drain(Sdb *, Sdb *); + +// Copy everything, including namespaces, from src to dst +RZ_API void sdb_copy(Sdb *src, Sdb *dst); + +RZ_API bool sdb_stats(Sdb *s, ut32 *disk, ut32 *mem); +RZ_API bool sdb_dump_hasnext(Sdb *s); + +typedef bool (*SdbForeachCallback)(void *user, const char *k, const char *v); +RZ_API bool sdb_foreach(Sdb *s, SdbForeachCallback cb, void *user); +RZ_API SdbList *sdb_foreach_list(Sdb *s, bool sorted); +RZ_API SdbList *sdb_foreach_list_filter_user(Sdb *s, SdbForeachCallback filter, bool sorted, void *user); +RZ_API SdbList *sdb_foreach_list_filter(Sdb *s, SdbForeachCallback filter, bool sorted); +RZ_API SdbList *sdb_foreach_match(Sdb *s, const char *expr, bool sorted); + +RZ_API int sdb_query(Sdb *s, const char *cmd); +RZ_API int sdb_queryf(Sdb *s, const char *fmt, ...); +RZ_API int sdb_query_lines(Sdb *s, const char *cmd); +RZ_API char *sdb_querys(Sdb *s, char *buf, size_t len, const char *cmd); +RZ_API char *sdb_querysf(Sdb *s, char *buf, size_t buflen, const char *fmt, ...); +RZ_API int sdb_query_file(Sdb *s, const char *file); +RZ_API bool sdb_exists(Sdb *, const char *key); +RZ_API bool sdb_remove(Sdb *, const char *key, ut32 cas); +RZ_API int sdb_unset(Sdb *, const char *key, ut32 cas); +RZ_API int sdb_unset_like(Sdb *s, const char *k); +RZ_API char **sdb_like(Sdb *s, const char *k, const char *v, SdbForeachCallback cb); + +// diffing +typedef struct sdb_diff_t { + const SdbList *path; + const char *k; + const char *v; // if null, k is a namespace + bool add; +} SdbDiff; + +// Format diff in a readable form into str. str, size and return are like in snprintf. +RZ_API int sdb_diff_format(char *str, int size, const SdbDiff *diff); + +typedef void (*SdbDiffCallback)(const SdbDiff *diff, void *user); + +// Returns true iff the contents of a and b are equal including contained namespaces +// If cb is non-null, it will be called subsequently with differences. +RZ_API bool sdb_diff(Sdb *a, Sdb *b, SdbDiffCallback cb, void *cb_user); + +// Gets a pointer to the value associated with `key`. +RZ_API char *sdb_get(Sdb *, const char *key, ut32 *cas); + +// Gets a pointer to the value associated with `key` and returns in `vlen` the +// length of the value string. +RZ_API char *sdb_get_len(Sdb *, const char *key, int *vlen, ut32 *cas); + +// Gets a const pointer to the value associated with `key` +RZ_API const char *sdb_const_get(Sdb *, const char *key, ut32 *cas); + +// Gets a const pointer to the value associated with `key` and returns in +// `vlen` the length of the value string. +RZ_API const char *sdb_const_get_len(Sdb *s, const char *key, int *vlen, ut32 *cas); +RZ_API int sdb_set(Sdb *, const char *key, const char *data, ut32 cas); +RZ_API int sdb_set_owned(Sdb *s, const char *key, char *val, ut32 cas); +RZ_API int sdb_concat(Sdb *s, const char *key, const char *value, ut32 cas); +RZ_API int sdb_uncat(Sdb *s, const char *key, const char *value, ut32 cas); +RZ_API int sdb_add(Sdb *s, const char *key, const char *val, ut32 cas); +RZ_API bool sdb_sync(Sdb *); +RZ_API void sdbkv_free(SdbKv *kv); + +/* num.c */ +RZ_API bool sdb_num_exists(Sdb *, const char *key); +RZ_API int sdb_num_base(const char *s); +RZ_API ut64 sdb_num_get(Sdb *s, const char *key, ut32 *cas); +RZ_API int sdb_num_set(Sdb *s, const char *key, ut64 v, ut32 cas); +RZ_API int sdb_num_add(Sdb *s, const char *key, ut64 v, ut32 cas); +RZ_API ut64 sdb_num_inc(Sdb *s, const char *key, ut64 n, ut32 cas); +RZ_API ut64 sdb_num_dec(Sdb *s, const char *key, ut64 n, ut32 cas); +RZ_API int sdb_num_min(Sdb *s, const char *key, ut64 v, ut32 cas); +RZ_API int sdb_num_max(Sdb *s, const char *key, ut64 v, ut32 cas); + +/* ptr */ +RZ_API int sdb_ptr_set(Sdb *db, const char *key, void *p, ut32 cas); +RZ_API void *sdb_ptr_get(Sdb *db, const char *key, ut32 *cas); + +/* create db */ +RZ_API bool sdb_disk_create(Sdb *s); +RZ_API bool sdb_disk_insert(Sdb *s, const char *key, const char *val); +RZ_API bool sdb_disk_finish(Sdb *s); +RZ_API bool sdb_disk_unlink(Sdb *s); + +/* plaintext sdb files */ +RZ_API bool sdb_text_save_fd(Sdb *s, int fd, bool sort); +RZ_API bool sdb_text_save(Sdb *s, const char *file, bool sort); +RZ_API bool sdb_text_load_buf(Sdb *s, char *buf, size_t sz); +RZ_API bool sdb_text_load(Sdb *s, const char *file); + +/* iterate */ +RZ_API void sdb_dump_begin(Sdb *s); +RZ_API SdbKv *sdb_dump_next(Sdb *s); +RZ_API bool sdb_dump_dupnext(Sdb *s, char *key, char **value, int *_vlen); + +/* journaling */ +RZ_API bool sdb_journal_close(Sdb *s); +RZ_API bool sdb_journal_open(Sdb *s); +RZ_API int sdb_journal_load(Sdb *s); +RZ_API bool sdb_journal_log(Sdb *s, const char *key, const char *val); +RZ_API bool sdb_journal_clear(Sdb *s); +RZ_API bool sdb_journal_unlink(Sdb *s); + +/* numeric */ +RZ_API char *sdb_itoa(ut64 n, char *s, int base); +RZ_API ut64 sdb_atoi(const char *s); +RZ_API const char *sdb_itoca(ut64 n); + +/* locking */ +RZ_API bool sdb_lock(const char *s); +RZ_API const char *sdb_lock_file(const char *f); +RZ_API void sdb_unlock(const char *s); +RZ_API bool sdb_unlink(Sdb *s); +RZ_API int sdb_lock_wait(RZ_UNUSED const char *s); + +/* expiration */ +RZ_API bool sdb_expire_set(Sdb *s, const char *key, ut64 expire, ut32 cas); +RZ_API ut64 sdb_expire_get(Sdb *s, const char *key, ut32 *cas); +RZ_API ut64 sdb_now(void); +RZ_API ut64 sdb_unow(void); +RZ_API ut32 sdb_hash(const char *key); +RZ_API ut32 sdb_hash_len(const char *key, ut32 *len); +RZ_API ut8 sdb_hash_byte(const char *s); + +/* json api */ +// RZ_API int sdb_js0n(const unsigned char *js, RangstrType len, RangstrType *out); +RZ_API bool sdb_isjson(const char *k); +RZ_API char *sdb_json_get_str(const char *json, const char *path); +RZ_API bool sdb_json_get_bool(const char *json, const char *path); + +RZ_API char *sdb_json_get(Sdb *s, const char *key, const char *p, ut32 *cas); +RZ_API bool sdb_json_set(Sdb *s, const char *k, const char *p, const char *v, ut32 cas); +RZ_API int sdb_json_num_get(Sdb *s, const char *k, const char *p, ut32 *cas); +RZ_API int sdb_json_num_set(Sdb *s, const char *k, const char *p, int v, ut32 cas); +RZ_API int sdb_json_num_dec(Sdb *s, const char *k, const char *p, int n, ut32 cas); +RZ_API int sdb_json_num_inc(Sdb *s, const char *k, const char *p, int n, ut32 cas); + +RZ_API char *sdb_json_indent(const char *s, const char *tab); +RZ_API char *sdb_json_unindent(const char *s); + +typedef struct { + char *buf; + size_t blen; + size_t len; +} SdbJsonString; + +RZ_API const char *sdb_json_format(SdbJsonString *s, const char *fmt, ...); +#define sdb_json_format_free(x) free((x)->buf) + +// namespace +RZ_API Sdb *sdb_ns(Sdb *s, const char *name, int create); +RZ_API Sdb *sdb_ns_path(Sdb *s, const char *path, int create); +RZ_API void sdb_ns_init(Sdb *s); +RZ_API void sdb_ns_free_all(Sdb *s); +RZ_API void sdb_ns_lock(Sdb *s, int lock, int depth); +RZ_API void sdb_ns_sync(Sdb *s); +RZ_API int sdb_ns_set(Sdb *s, const char *name, Sdb *r); +RZ_API bool sdb_ns_unset(Sdb *s, const char *name, Sdb *r); + +// array +RZ_API bool sdb_array_contains(Sdb *s, const char *key, const char *val, ut32 *cas); +RZ_API bool sdb_array_contains_num(Sdb *s, const char *key, ut64 val, ut32 *cas); +RZ_API int sdb_array_indexof(Sdb *s, const char *key, const char *val, ut32 cas); +RZ_API int sdb_array_set(Sdb *s, const char *key, int idx, const char *val, ut32 cas); +RZ_API int sdb_array_set_num(Sdb *s, const char *key, int idx, ut64 val, ut32 cas); +RZ_API bool sdb_array_append(Sdb *s, const char *key, const char *val, ut32 cas); +RZ_API bool sdb_array_append_num(Sdb *s, const char *key, ut64 val, ut32 cas); +RZ_API bool sdb_array_prepend(Sdb *s, const char *key, const char *val, ut32 cas); +RZ_API bool sdb_array_prepend_num(Sdb *s, const char *key, ut64 val, ut32 cas); +RZ_API char *sdb_array_get(Sdb *s, const char *key, int idx, ut32 *cas); +RZ_API ut64 sdb_array_get_num(Sdb *s, const char *key, int idx, ut32 *cas); +RZ_API int sdb_array_get_idx(Sdb *s, const char *key, const char *val, ut32 cas); // agetv +RZ_API int sdb_array_insert(Sdb *s, const char *key, int idx, const char *val, ut32 cas); +RZ_API int sdb_array_insert_num(Sdb *s, const char *key, int idx, ut64 val, ut32 cas); +RZ_API int sdb_array_unset(Sdb *s, const char *key, int n, ut32 cas); // leaves empty bucket +RZ_API int sdb_array_delete(Sdb *s, const char *key, int n, ut32 cas); +RZ_API void sdb_array_sort(Sdb *s, const char *key, ut32 cas); +RZ_API void sdb_array_sort_num(Sdb *s, const char *key, ut32 cas); +// set + +// Adds string `val` at the end of array `key`. +RZ_API int sdb_array_add(Sdb *s, const char *key, const char *val, ut32 cas); + +// Adds number `val` at the end of array `key`. +RZ_API int sdb_array_add_num(Sdb *s, const char *key, ut64 val, ut32 cas); + +// Adds string `val` in the sorted array `key`. +RZ_API int sdb_array_add_sorted(Sdb *s, const char *key, const char *val, ut32 cas); + +// Adds number `val` in the sorted array `key`. +RZ_API int sdb_array_add_sorted_num(Sdb *s, const char *key, ut64 val, ut32 cas); + +// Removes the string `val` from the array `key`. +RZ_API int sdb_array_remove(Sdb *s, const char *key, const char *val, ut32 cas); + +// Removes the number `val` from the array `key`. +RZ_API int sdb_array_remove_num(Sdb *s, const char *key, ut64 val, ut32 cas); + +// helpers +RZ_API char *sdb_anext(char *str, char **next); +RZ_API const char *sdb_const_anext(const char *str); +RZ_API int sdb_alen(const char *str); +RZ_API int sdb_alen_ignore_empty(const char *str); +RZ_API int sdb_array_size(Sdb *s, const char *key); +RZ_API int sdb_array_length(Sdb *s, const char *key); + +int sdb_array_list(Sdb *s, const char *key); + +// Adds the string `val` to the start of array `key`. +RZ_API bool sdb_array_push(Sdb *s, const char *key, const char *val, ut32 cas); + +// Returns the string at the start of array `key` or +// NULL if there are no elements. +RZ_API char *sdb_array_pop(Sdb *s, const char *key, ut32 *cas); + +// Adds the number `val` to the start of array `key`. +RZ_API int sdb_array_push_num(Sdb *s, const char *key, ut64 num, ut32 cas); + +// Returns the number at the start of array `key`. +RZ_API ut64 sdb_array_pop_num(Sdb *s, const char *key, ut32 *cas); + +RZ_API char *sdb_array_pop_head(Sdb *s, const char *key, ut32 *cas); +RZ_API char *sdb_array_pop_tail(Sdb *s, const char *key, ut32 *cas); + +typedef void (*SdbHook)(Sdb *s, void *user, const char *k, const char *v); + +RZ_API void sdb_global_hook(SdbHook hook, void *user); +RZ_API bool sdb_hook(Sdb *s, SdbHook cb, void *user); +RZ_API bool sdb_unhook(Sdb *s, SdbHook h); +RZ_API int sdb_hook_call(Sdb *s, const char *k, const char *v); +RZ_API void sdb_hook_free(Sdb *s); +/* Util.c */ +RZ_API int sdb_isnum(const char *s); +RZ_API bool sdb_isempty(Sdb *s); + +RZ_API const char *sdb_type(const char *k); +RZ_API bool sdb_match(const char *str, const char *glob); +RZ_API int sdb_bool_set(Sdb *db, const char *str, bool v, ut32 cas); +RZ_API bool sdb_bool_get(Sdb *db, const char *str, ut32 *cas); + +// base64 +RZ_API ut8 *sdb_decode(const char *in, int *len); +RZ_API char *sdb_encode(const ut8 *bin, int len); +RZ_API void sdb_encode_raw(char *bout, const ut8 *bin, int len); +RZ_API int sdb_decode_raw(ut8 *bout, const char *bin, int len); + +// binfmt +RZ_API char *sdb_fmt(const char *fmt, ...) SDB_PRINTF_CHECK(1, 2); +RZ_API int sdb_fmt_init(void *p, const char *fmt); +RZ_API void sdb_fmt_free(void *p, const char *fmt); +RZ_API int sdb_fmt_tobin(const char *_str, const char *fmt, void *stru); +RZ_API char *sdb_fmt_tostr(void *stru, const char *fmt); +RZ_API char **sdb_fmt_array(const char *list); +RZ_API ut64 *sdb_fmt_array_num(const char *list); + +// raw array helpers +RZ_API char *sdb_array_compact(char *p); +RZ_API char *sdb_aslice(char *out, int from, int to); +#define sdb_aforeach(x, y) \ + { \ + char *next; \ + if (y) \ + for (x = y;;) { \ + x = sdb_anext(x, &next); +#define sdb_aforeach_next(x) \ + if (!next) \ + break; \ + *(next - 1) = ','; \ + x = next; \ + } \ + } + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librz/util/sdb/src/sdb_private.h b/librz/util/sdb/src/sdb_private.h new file mode 100644 index 00000000000..41d53556c69 --- /dev/null +++ b/librz/util/sdb/src/sdb_private.h @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#ifndef SDB_PRIVATE_H_ +#define SDB_PRIVATE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HAVE_HEADER_SYS_MMAN_H +#define HAVE_HEADER_SYS_MMAN_H HAVE_MMAN +#endif + +#define SDB_V_NOT(op, fail_ret) \ + if ((op) == (fail_ret)) \ + eprintf(#op " at %s:%d failed: %s\n", __FILE__, __LINE__, strerror(errno)) +#define write_(fd, buf, count) SDB_V_NOT(write(fd, buf, count), -1) +#define read_(fd, buf, count) SDB_V_NOT(read(fd, buf, count), -1) + +static inline int seek_set(int fd, off_t pos) { + return ((fd == -1) || (lseek(fd, (off_t)pos, SEEK_SET) == -1)) ? 0 : 1; +} + +static inline void ut32_pack(char s[4], ut32 u) { + s[0] = u & 255; + u >>= 8; + s[1] = u & 255; + u >>= 8; + s[2] = u & 255; + s[3] = u >> 8; +} + +static inline void ut32_pack_big(char s[4], ut32 u) { + s[3] = u & 255; + u >>= 8; + s[2] = u & 255; + u >>= 8; + s[1] = u & 255; + s[0] = u >> 8; +} + +static inline void ut32_unpack(char s[4], ut32 *u) { + ut32 result = 0; + result = (ut8)s[3]; + result <<= 8; + result += (ut8)s[2]; + result <<= 8; + result += (ut8)s[1]; + result <<= 8; + result += (ut8)s[0]; + *u = result; +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librz/util/sdb/src/sdbht.c b/librz/util/sdb/src/sdbht.c new file mode 100644 index 00000000000..d187173ec40 --- /dev/null +++ b/librz/util/sdb/src/sdbht.c @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2011-2020 pancake +// SPDX-License-Identifier: MIT + +#include "sdbht.h" + +void sdbkv_fini(SdbKv *kv) { + free(kv->base.key); + free(kv->base.value); +} + +RZ_API HtPP *sdb_ht_new(void) { + HtPP *ht = ht_pp_new((HtPPDupValue)strdup, (HtPPKvFreeFunc)sdbkv_fini, (HtPPCalcSizeV)strlen); + if (ht) { + ht->opt.elem_size = sizeof(SdbKv); + } + return ht; +} + +static bool sdb_ht_internal_insert(HtPP *ht, const char *key, const char *value, bool update) { + if (!ht || !key || !value) { + return false; + } + SdbKv kvp = { { 0 } }; + kvp.base.key = strdup((void *)key); + if (!kvp.base.key) { + goto err; + } + kvp.base.value = strdup((void *)value); + if (!kvp.base.value) { + goto err; + } + kvp.base.key_len = strlen(kvp.base.key); + kvp.base.value_len = strlen(kvp.base.value); + kvp.expire = 0; + return ht_pp_insert_kv(ht, (HtPPKv *)&kvp, update); + +err: + free(kvp.base.key); + free(kvp.base.value); + return false; +} + +RZ_API bool sdb_ht_insert(HtPP *ht, const char *key, const char *value) { + return sdb_ht_internal_insert(ht, key, value, false); +} + +RZ_API bool sdb_ht_insert_kvp(HtPP *ht, SdbKv *kvp, bool update) { + return ht_pp_insert_kv(ht, (HtPPKv *)kvp, update); +} + +RZ_API bool sdb_ht_update(HtPP *ht, const char *key, const char *value) { + return sdb_ht_internal_insert(ht, key, value, true); +} + +RZ_API SdbKv *sdb_ht_find_kvp(HtPP *ht, const char *key, bool *found) { + return (SdbKv *)ht_pp_find_kv(ht, key, found); +} + +RZ_API char *sdb_ht_find(HtPP *ht, const char *key, bool *found) { + return (char *)ht_pp_find(ht, key, found); +} + +RZ_API void sdb_ht_free(HtPP *ht) { + ht_pp_free(ht); +} + +RZ_API bool sdb_ht_delete(HtPP *ht, const char *key) { + return ht_pp_delete(ht, key); +} diff --git a/librz/util/sdb/src/sdbht.h b/librz/util/sdb/src/sdbht.h new file mode 100644 index 00000000000..41bad7390d0 --- /dev/null +++ b/librz/util/sdb/src/sdbht.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#ifndef __SDB_HT_H +#define __SDB_HT_H + +#include "ht_pp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** keyvalue pair **/ +typedef struct sdb_kv { + // sub of HtPPKv so we can cast safely + HtPPKv base; + ut32 cas; + ut64 expire; +} SdbKv; + +static inline char *sdbkv_key(const SdbKv *kv) { + return (char *)kv->base.key; +} + +static inline char *sdbkv_value(const SdbKv *kv) { + return (char *)kv->base.value; +} + +static inline ut32 sdbkv_key_len(const SdbKv *kv) { + return kv->base.key_len; +} + +static inline ut32 sdbkv_value_len(const SdbKv *kv) { + return kv->base.value_len; +} + +RZ_API SdbKv *sdbkv_new2(const char *k, int kl, const char *v, int vl); +RZ_API SdbKv *sdbkv_new(const char *k, const char *v); +extern RZ_API void sdbkv_free(SdbKv *kv); + +extern RZ_API ut32 sdb_hash(const char *key); + +RZ_API HtPP *sdb_ht_new(void); +// Destroy a hashtable and all of its entries. +RZ_API void sdb_ht_free(HtPP *ht); +// Insert a new Key-Value pair into the hashtable. If the key already exists, returns false. +RZ_API bool sdb_ht_insert(HtPP *ht, const char *key, const char *value); +// Insert a new Key-Value pair into the hashtable, or updates the value if the key already exists. +RZ_API bool sdb_ht_insert_kvp(HtPP *ht, SdbKv *kvp, bool update); +// Insert a new Key-Value pair into the hashtable, or updates the value if the key already exists. +RZ_API bool sdb_ht_update(HtPP *ht, const char *key, const char *value); +// Delete a key from the hashtable. +RZ_API bool sdb_ht_delete(HtPP *ht, const char *key); +// Find the value corresponding to the matching key. +RZ_API char *sdb_ht_find(HtPP *ht, const char *key, bool *found); +// Find the KeyValuePair corresponding to the matching key. +RZ_API SdbKv *sdb_ht_find_kvp(HtPP *ht, const char *key, bool *found); + +#ifdef __cplusplus +} +#endif + +#endif // __SDB_HT_H diff --git a/librz/util/sdb/src/set.c b/librz/util/sdb/src/set.c new file mode 100644 index 00000000000..27e13c2c725 --- /dev/null +++ b/librz/util/sdb/src/set.c @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2019 pancake +// SPDX-License-Identifier: MIT + +#include "set.h" + +// p + +RZ_API SetP *set_p_new(void) { + return ht_pp_new0(); +} + +RZ_API void set_p_add(SetP *s, const void *u) { + ht_pp_insert(s, u, (void *)1); +} + +RZ_API bool set_p_contains(SetP *s, const void *u) { + return ht_pp_find(s, u, NULL) != NULL; +} + +RZ_API void set_p_delete(SetP *s, const void *u) { + ht_pp_delete(s, u); +} + +RZ_API void set_p_free(SetP *p) { + ht_pp_free((HtPP *)p); +} + +// u + +RZ_API SetU *set_u_new(void) { + return (SetU *)ht_up_new0(); +} + +RZ_API void set_u_add(SetU *s, ut64 u) { + ht_up_insert(s, u, (void *)1); +} + +RZ_API bool set_u_contains(SetU *s, ut64 u) { + return ht_up_find(s, u, NULL) != NULL; +} + +RZ_API void set_u_delete(SetU *s, ut64 u) { + ht_up_delete(s, u); +} + +RZ_API void set_u_free(SetU *s) { + ht_up_free(s); +} diff --git a/librz/util/sdb/src/set.h b/librz/util/sdb/src/set.h new file mode 100644 index 00000000000..7bda2392d1b --- /dev/null +++ b/librz/util/sdb/src/set.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#ifndef SDB_SET_H +#define SDB_SET_H + +#include "ht_pp.h" +#include "ht_up.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef HtPP SetP; + +RZ_API SetP *set_p_new(void); +RZ_API void set_p_add(SetP *p, const void *u); +RZ_API bool set_p_contains(SetP *s, const void *u); +RZ_API void set_p_delete(SetP *s, const void *u); +RZ_API void set_p_free(SetP *p); + +typedef HtUP SetU; + +RZ_API SetU *set_u_new(void); +RZ_API void set_u_add(SetU *p, ut64 u); +RZ_API bool set_u_contains(SetU *s, ut64 u); +RZ_API void set_u_delete(SetU *s, ut64 u); +RZ_API void set_u_free(SetU *p); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librz/util/sdb/src/text.c b/librz/util/sdb/src/text.c new file mode 100644 index 00000000000..c1033a56888 --- /dev/null +++ b/librz/util/sdb/src/text.c @@ -0,0 +1,439 @@ +// SPDX-FileCopyrightText: 2020 thestr4ng3r +// SPDX-License-Identifier: MIT + +#include "sdb.h" + +#include +#include +#include +#if HAVE_HEADER_SYS_MMAN_H +#include +#endif +#include "sdb_private.h" + +/** + * ******************** + * Plaintext SDB Format + * ******************** + * + * Files are UTF-8 and use '\n' line endings. Always. + * + * Lines starting with '/' denote the path of the namespace for the following data: + * + * /some/namespace + * + * The default path is root, just a slash also means root. + * These paths are always absolute from the root. Characters that must be escaped in a path are: '/', '\\', '\n', '\r': + * + * /s\/ome/name\nspa\\ce + * + * SDB entries are written each as a single k=v line: + * + * somekey=somevalue + * + * To distinguish these from path lines, if there is a leading '/' in the key, it must be escaped + * (slashes later in the line don't have to be escaped): + * + * \/slashedkey=somevalue + * + * Other than that, at any postion, '\\', '\n' and '\r' must be escaped: + * + * some\\key=some\nvalue + * + * In the key, '=' must also be escaped (not necessary in the value): + * + * some\=key=some=value + * + * -------- + * Example: + * + * / + * key=intheroot + * \/slashedkey=somevalue + * some\\key=some\nvalue + * some\=key=some=value + * + * /subns + * some=stuff in the sub-namespace + * + * /subns/deeper + * this=is in /subns/deeper + * + */ + +static int cmp_ns(const void *a, const void *b) { + const SdbNs *nsa = a; + const SdbNs *cia = b; + return strcmp(nsa->name, cia->name); +} + +// n = position we are currently looking at +// p = position until we have already written everything +// flush a block of text that doesn't have to be escaped +#define FLUSH \ + do { \ + if (p != n) { \ + write_(fd, p, n - p); \ + p = n; \ + } \ + } while (0) +// write and escape a string from str to fd +#define ESCAPE_LOOP(fd, str, escapes) \ + do { \ + const char *p = str; \ + const char *n = p; \ + while (*n) { \ + switch (*n) { escapes } \ + n++; \ + } \ + FLUSH; \ + } while (0) +#define ESCAPE(c, repl, replsz) \ + case c: \ + FLUSH; \ + p++; \ + write_(fd, "\\" repl, replsz + 1); \ + break; + +static void write_path(int fd, SdbList *path) { + write_(fd, "/", 1); // always print a /, even if path is empty + SdbListIter *it; + const char *path_token; + bool first = true; + ls_foreach (path, it, path_token) { + if (first) { + first = false; + } else { + write_(fd, "/", 1); + } + ESCAPE_LOOP(fd, path_token, + ESCAPE('\\', "\\", 1); + ESCAPE('/', "/", 1); + ESCAPE('\n', "n", 1); + ESCAPE('\r', "r", 1);); + } +} + +static void write_key(int fd, const char *k) { + // escape leading '/' + if (*k == '/') { + write_(fd, "\\", 1); + } + ESCAPE_LOOP(fd, k, + ESCAPE('\\', "\\", 1); + ESCAPE('=', "=", 1); + ESCAPE('\n', "n", 1); + ESCAPE('\r', "r", 1);); +} + +static void write_value(int fd, const char *v) { + ESCAPE_LOOP(fd, v, + ESCAPE('\\', "\\", 1); + ESCAPE('\n', "n", 1); + ESCAPE('\r', "r", 1);); +} +#undef FLUSH +#undef ESCAPE_LOOP +#undef ESCAPE + +static bool save_kv_cb(void *user, const char *k, const char *v) { + int fd = *(int *)user; + write_key(fd, k); + write_(fd, "=", 1); + write_value(fd, v); + write_(fd, "\n", 1); + return true; +} + +static bool text_save(Sdb *s, int fd, bool sort, SdbList *path) { + // path + write_path(fd, path); + write_(fd, "\n", 1); + + // k=v entries + if (sort) { + SdbList *l = sdb_foreach_list(s, true); + SdbKv *kv; + SdbListIter *it; + ls_foreach (l, it, kv) { + save_kv_cb(&fd, sdbkv_key(kv), sdbkv_value(kv)); + } + ls_free(l); + } else { + // This is faster when sorting is not needed. + sdb_foreach(s, save_kv_cb, &fd); + } + + // sub-namespaces + SdbList *l = s->ns; + if (sort) { + l = ls_clone(l); + ls_sort(l, cmp_ns); + } + SdbNs *ns; + SdbListIter *it; + ls_foreach (l, it, ns) { + write_(fd, "\n", 1); + ls_push(path, ns->name); + text_save(ns->sdb, fd, sort, path); + ls_pop(path); + } + if (l != s->ns) { + ls_free(l); + } + + return true; +} +#undef write_ + +RZ_API bool sdb_text_save_fd(Sdb *s, int fd, bool sort) { + SdbList *path = ls_new(); + if (!path) { + return false; + } + bool r = text_save(s, fd, sort, path); + ls_free(path); + return r; +} + +RZ_API bool sdb_text_save(Sdb *s, const char *file, bool sort) { + int fd = open(file, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); + if (fd < 0) { + return false; + } + bool r = sdb_text_save_fd(s, fd, sort); + close(fd); + return r; +} + +typedef enum { + STATE_NEWLINE, + STATE_PATH, + STATE_KEY, + STATE_VALUE +} LoadState; + +typedef struct { + bool eof; + char *buf; + size_t bufsz; + Sdb *root_db; + Sdb *cur_db; // current namespace, changes when encountering a path line + size_t pos; // current processing position in the buffer + size_t line_begin; + size_t token_begin; // beginning of the currently processed token in the buffer + size_t shift; // amount to shift chars to the left (from unescaping) + SdbList /**/ *path; + LoadState state; + bool unescape; // whether the prev char was a backslash, i.e. the current one is escaped +} LoadCtx; + +// to be called at the end of a line. +// save all the data processed from the line into the database. +// assumes that the ctx->buf is allocated until ctx->buf[ctx->pos] inclusive! +static void load_process_line(LoadCtx *ctx) { + ctx->unescape = false; + // finish up the line + ctx->buf[ctx->pos - ctx->shift] = '\0'; + switch (ctx->state) { + case STATE_PATH: { + ls_push(ctx->path, (void *)ctx->token_begin); + SdbListIter *it; + void *token_off_tmp; + ctx->cur_db = ctx->root_db; + ls_foreach (ctx->path, it, token_off_tmp) { + size_t token_off = (size_t)token_off_tmp; + if (!ctx->buf[token_off]) { + continue; + } + ctx->cur_db = sdb_ns(ctx->cur_db, ctx->buf + token_off, 1); + if (!ctx->cur_db) { + ctx->cur_db = ctx->root_db; + break; + } + } + ls_destroy(ctx->path); + break; + } + case STATE_VALUE: { + const char *k = ctx->buf + ctx->line_begin; + const char *v = ctx->buf + ctx->token_begin; + if (!*k || !*v) { + break; + } + sdb_set(ctx->cur_db, k, v, 0); + break; + } + default: + break; + } + // prepare for next line + ctx->shift = 0; + ctx->state = STATE_NEWLINE; +} + +static inline char unescape_raw_char(char c) { + switch (c) { + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + default: + return c; + } +} + +static void load_process_single_char(LoadCtx *ctx) { + char c = ctx->buf[ctx->pos]; + if (c == '\n' || c == '\r') { + load_process_line(ctx); + ctx->pos++; + return; + } + + if (ctx->state == STATE_NEWLINE) { + ctx->line_begin = ctx->pos; + // at the start of a line, decide whether it's a path or a k=v + // by whether there is a leading slash. + if (c == '/') { + ctx->state = STATE_PATH; + ctx->token_begin = ctx->pos + 1; + ctx->pos++; + c = ctx->buf[ctx->pos]; + return; + } + ctx->state = STATE_KEY; + } + + if (ctx->unescape) { + ctx->buf[ctx->pos - ctx->shift] = unescape_raw_char(c); + ctx->unescape = false; + } else if (c == '\\') { + // got a backslash, the next char, unescape in the next iteration or die! + ctx->shift++; + ctx->unescape = true; + } else if (ctx->state == STATE_PATH && c == '/') { + // new path token + ctx->buf[ctx->pos - ctx->shift] = '\0'; + ls_push(ctx->path, (void *)ctx->token_begin); + ctx->token_begin = ctx->pos + 1; + ctx->shift = 0; + } else if (ctx->state == STATE_KEY && c == '=') { + // switch from key to value mode + ctx->buf[ctx->pos - ctx->shift] = '\0'; + ctx->token_begin = ctx->pos + 1; + ctx->shift = 0; + ctx->state = STATE_VALUE; + } else if (ctx->shift) { + // just some char, shift it back if necessary + ctx->buf[ctx->pos - ctx->shift] = c; + } + ctx->pos++; +} + +static bool load_process_final_line(LoadCtx *ctx) { + // load_process_line needs ctx.buf[ctx.pos] to be allocated! + // so we need room for one additional byte after the buffer. + size_t linesz = ctx->bufsz - ctx->line_begin; + char *linebuf = malloc(linesz + 1); + if (!linebuf) { + return false; + } + memcpy(linebuf, ctx->buf + ctx->line_begin, linesz); + ctx->buf = linebuf; + // shift everything by the size we skipped + ctx->bufsz -= ctx->line_begin; + ctx->pos = linesz; + ctx->token_begin -= ctx->line_begin; + SdbListIter *it; + void *token_off_tmp; + ls_foreach (ctx->path, it, token_off_tmp) { + it->data = (void *)((size_t)token_off_tmp - ctx->line_begin); + } + ctx->line_begin = 0; + load_process_line(ctx); + free(linebuf); + ctx->buf = NULL; + return true; +} + +static void load_ctx_fini(LoadCtx *ctx) { + ls_free(ctx->path); +} + +static bool load_ctx_init(LoadCtx *ctx, Sdb *s, char *buf, size_t sz) { + ctx->eof = false; + ctx->buf = buf; + ctx->bufsz = sz; + ctx->root_db = s; + ctx->cur_db = s; + ctx->pos = 0; + ctx->line_begin = 0; + ctx->token_begin = 0; + ctx->shift = 0; + ctx->path = ls_new(); + ctx->state = STATE_NEWLINE; + ctx->unescape = false; + if (!ctx->buf || !ctx->path) { + load_ctx_fini(ctx); + return false; + } + return true; +} + +RZ_API bool sdb_text_load_buf(Sdb *s, char *buf, size_t sz) { + if (!sz) { + return true; + } + LoadCtx ctx; + if (!load_ctx_init(&ctx, s, buf, sz)) { + return false; + } + bool ret = true; + while (ctx.pos < ctx.bufsz) { + load_process_single_char(&ctx); + } + if (ctx.line_begin < ctx.bufsz && ctx.state != STATE_NEWLINE) { + load_process_final_line(&ctx); + } + load_ctx_fini(&ctx); + return ret; +} + +RZ_API bool sdb_text_load(Sdb *s, const char *file) { + int fd = open(file, O_RDONLY | O_BINARY); + if (fd < 0) { + return false; + } + bool r = false; + struct stat st; + if (fstat(fd, &st) || !st.st_size) { + goto beach; + } +#if HAVE_HEADER_SYS_MMAN_H + char *x = mmap(0, st.st_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + if (x == MAP_FAILED) { + goto beach; + } +#else + char *x = calloc(1, st.st_size); + if (!x) { + goto beach; + } + if (read(fd, x, st.st_size) != st.st_size) { + free(x); + goto beach; + } +#endif + r = sdb_text_load_buf(s, x, st.st_size); +#if HAVE_HEADER_SYS_MMAN_H + munmap(x, st.st_size); +#else + free(x); +#endif +beach: + close(fd); + return r; +} diff --git a/librz/util/sdb/src/util.c b/librz/util/sdb/src/util.c new file mode 100644 index 00000000000..7bb02d94ca6 --- /dev/null +++ b/librz/util/sdb/src/util.c @@ -0,0 +1,283 @@ +// SPDX-FileCopyrightText: 2011-2018 pancake +// SPDX-License-Identifier: MIT + +#include "sdb.h" +#include + +RZ_API ut32 sdb_hash_len(const char *s, ut32 *len) { + ut32 h = CDB_HASHSTART; + ut32 count = 0; + if (s) { + while (*s) { + h = (h + (h << 5)) ^ *s++; + count++; + } + } + if (len) { + *len = count; + } + return h; +} + +RZ_API ut32 sdb_hash(const char *s) { + return sdb_hash_len(s, NULL); +} + +RZ_API ut8 sdb_hash_byte(const char *s) { + const ut32 hash = sdb_hash_len(s, NULL); + const ut8 *h = (const ut8 *)&hash; + return h[0] ^ h[1] ^ h[2] ^ h[3]; +} + +RZ_API const char *sdb_itoca(ut64 n) { + return sdb_itoa(n, sdb_fmt(NULL), 16); +} + +// assert (sizeof (s)>64) +// if s is null, the returned pointer must be freed!! +RZ_API char *sdb_itoa(ut64 n, char *s, int base) { + static const char *lookup = "0123456789abcdef"; + const int imax = 62; + int i = imax, copy_string = 1, alloc = 0; + if (s) { + *s = 0; + } else { + alloc = 1; + s = malloc(64); + if (!s) { + return NULL; + } + } + if (base < 0) { + copy_string = 0; + base = -base; + } + if ((base > 16) || (base < 1)) { + if (alloc) { + free(s); + } + return NULL; + } + if (!n) { + strcpy(s, "0"); + return s; + } + s[imax + 1] = '\0'; + if (base <= 10) { + for (; n && i > 0; n /= base) { + s[i--] = (n % base) + '0'; + } + } else { + for (; n && i > 0; n /= base) { + s[i--] = lookup[(n % base)]; + } + if (i != imax) { + s[i--] = 'x'; + } + s[i--] = '0'; + } + if (copy_string || alloc) { + // unnecessary memmove in case we use the return value + // return s + i + 1; + memmove(s, s + i + 1, strlen(s + i + 1) + 1); + return s; + } + return s + i + 1; +} + +RZ_API ut64 sdb_atoi(const char *s) { + char *p; + ut64 ret; + if (!s || *s == '-') { + return 0LL; + } + ret = strtoull(s, &p, 0); + return p ? ret : 0LL; +} + +// NOTE: Reuses memory. probably not bindings friendly.. +RZ_API char *sdb_array_compact(char *p) { + char *e; + // remove empty elements + while (*p) { + if (!strncmp(p, ",,", 2)) { + p++; + for (e = p + 1; *e == ','; e++) { + }; + memmove(p, e, strlen(e) + 1); + } else { + p++; + } + } + return p; +} + +// NOTE: Reuses memory. probably not bindings friendly.. +RZ_API char *sdb_aslice(char *out, int from, int to) { + int len, idx = 0; + char *str = NULL; + char *end = NULL; + char *p = out; + if (from >= to) { + return NULL; + } + while (*p) { + if (!str && idx == from) { + str = p; + } + if (idx == to) { + end = p; + break; + } + if (*p == ',') { + idx++; + } + p++; + } + if (str) { + if (!end) { + end = str + strlen(str); + } + len = (size_t)(end - str); + memmove(out, str, len); + out[len] = 0; + return out; + } + return NULL; +} + +// TODO: find better name for it +// TODO: optimize, because this is the main bottleneck for sdb_array_set() +RZ_API int sdb_alen(const char *str) { + int len = 1; + const char *n, *p = str; + if (!p || !*p) { + return 0; + } + for (len = 0;; len++) { + n = strchr(p, SDB_RS); + if (!n) { + break; + } + p = n + 1; + } + return ++len; +} + +RZ_API int sdb_alen_ignore_empty(const char *str) { + int len = 1; + const char *n, *p = str; + if (!p || !*p) { + return 0; + } + while (*p == SDB_RS) { + p++; + } + for (len = 0;;) { + n = strchr(p, SDB_RS); + if (!n) { + break; + } + p = n + 1; + if (*(p) == SDB_RS) { + continue; + } + len++; + } + if (*p) + len++; + return len; +} + +RZ_API char *sdb_anext(char *str, char **next) { + char *nxt, *p = strchr(str, SDB_RS); + if (p) { + *p = 0; + nxt = p + 1; + } else { + nxt = NULL; + } + if (next) { + *next = nxt; + } + return str; +} + +RZ_API const char *sdb_const_anext(const char *str) { + const char *p = strchr(str, SDB_RS); + return p ? p + 1 : NULL; +} + +RZ_API ut64 sdb_now(void) { + ut64 usec = rz_time_now(); + return usec / 1000000; +} + +RZ_API int sdb_isnum(const char *s) { + const char vs = *s; + return ((vs == '-' || vs == '+') || (vs >= '0' && vs <= '9')); +} + +RZ_API int sdb_num_base(const char *s) { + if (!s) { + return SDB_NUM_BASE; + } + if (!strncmp(s, "0x", 2)) { + return 16; + } + return (*s == '0' && s[1]) ? 8 : 10; +} + +RZ_API const char *sdb_type(const char *k) { + if (!k || !*k) { + return "undefined"; + } + if (sdb_isnum(k)) { + return "number"; + } + if (sdb_isjson(k)) { + return "json"; + } + if (strchr(k, ',')) { + return "array"; + } + if (!strcmp(k, "true") || !strcmp(k, "false")) { + return "boolean"; + } + return "string"; +} + +// TODO: check all the values +RZ_API bool sdb_isjson(const char *k) { + int level = 0; + bool quotes = false; + if (!k || (*k != '{' && *k != '[')) { + return false; + } + for (; *k; k++) { + if (quotes) { + if (*k == '"') { + quotes = false; + } + continue; + } + switch (*k) { + case '"': + quotes = true; + break; + case '[': + case '{': + level++; + break; + case ']': + case '}': + level--; + if (level < 0) { + /* invalid json */ + return false; + } + break; + } + } + return (!quotes && !level); +} diff --git a/meson.build b/meson.build index 68f5cd4d98a..441e4eff490 100644 --- a/meson.build +++ b/meson.build @@ -69,17 +69,7 @@ message('rizin lib version: ' + rizin_libversion) # system dependencies cc = meson.get_compiler('c') -# required for linux -ldl = cc.find_library('dl', required: false, static: is_static_build) -th = dependency('threads', required: false, static: is_static_build) -have_pthread = th.found() and host_machine.system() != 'windows' -utl = cc.find_library('util', required: false, static: is_static_build) -if host_machine.system() == 'sunos' - # workaround for Solaris until https://github.com/mesonbuild/meson/issues/4328 is fixed - mth = declare_dependency(link_args: '-lm') -else - mth = cc.find_library('m', required: false, static: is_static_build) -endif +cc_native = meson.get_compiler('c', native: true) platform_deps = [] platform_inc = ['.', 'librz', 'librz/include'] @@ -368,133 +358,173 @@ endif message('RZ_CHECKS_LEVEL: @0@'.format(checks_level)) userconf = configuration_data() -userconf.set('RZ_CHECKS_LEVEL', checks_level) -userconf.set10('IS_PORTABLE', get_option('portable')) -userconf.set10('HAVE_LIB_MAGIC', sys_magic.found()) -userconf.set10('USE_LIB_MAGIC', sys_magic.found()) -userconf.set10('HAVE_LIB_XXHASH', xxhash_dep.found()) -userconf.set10('USE_LIB_XXHASH', xxhash_dep.found()) -userconf.set10('DEBUGGER', has_debugger) -userconf.set('PREFIX', rizin_prefix) -userconf.set('BINDIR', rizin_bindir) -userconf.set('BINDIR_DEPTH', bindir_depth) -userconf.set('LIBDIR', rizin_libdir) -userconf.set('INCLUDEDIR', rizin_incdir) -userconf.set('DATADIR_RZ', rizin_datdir_rz) -is_ppc = host_machine.cpu_family() == 'ppc' or host_machine.cpu_family() == 'ppc64' -userconf.set10('HAVE_JEMALLOC', host_machine.system() != 'windows' and (host_machine.system() != 'darwin' or not is_ppc)) -userconf.set('DATADIR', rizin_datdir) -userconf.set('WWWROOT', rizin_wwwroot) -userconf.set('SDB', rizin_sdb) -userconf.set('SIGDB', rizin_sigdb) -userconf.set('THEMES', rizin_themes) -userconf.set('FORTUNES', rizin_fortunes) -userconf.set('FLAGS', rizin_flags) -userconf.set('HUD', rizin_hud) -userconf.set('PLUGINS', rizin_plugins) -userconf.set('BINDINGS', rizin_bindings) -userconf.set10('HAVE_OPENSSL', sys_openssl.found()) -userconf.set10('HAVE_LIBUV', libuv_dep.found()) -userconf.set10('WANT_DYLINK', true) -userconf.set10('HAVE_PTRACE', have_ptrace) -userconf.set10('USE_PTRACE_WRAP', use_ptrace_wrap) -userconf.set10('WITH_GPL', get_option('use_gpl')) -userconf.set10('WITH_SWIFT_DEMANGLER', get_option('use_swift_demangler')) -userconf.set10('RZ_BUILD_DEBUG', get_option('buildtype').startswith('debug')) -ok = cc.has_header_symbol('sys/personality.h', 'ADDR_NO_RANDOMIZE') -userconf.set10('HAVE_DECL_ADDR_NO_RANDOMIZE', ok) -ok = cc.has_header_symbol('sys/procctl.h', 'PROC_ASLR_CTL') -userconf.set10('HAVE_DECL_PROCCTL_ASLR_CTL', ok) -userconf.set10('HAVE_THREADS', th.found()) -userconf.set10('HAVE_PTHREAD', have_pthread) -userconf.set10('HAVE_LZMA', get_option('use_lzma')) -userconf.set10('HAVE_ZLIB', get_option('use_zlib')) - -if host_machine.system() == 'freebsd' or host_machine.system() == 'dragonfly' - add_project_link_arguments('-Wl,--unresolved-symbols,ignore-in-object-files', language: 'c') - add_project_link_arguments('-Wl,--allow-shlib-undefined', language: 'c') -endif - -lrt = dependency('', required: false) -if not cc.has_function('clock_gettime', prefix: '#include ') and cc.has_header_symbol('features.h', '__GLIBC__') - lrt = cc.find_library('rt', required: true, static: is_static_build) -endif - -have_lrt = not ['windows', 'darwin', 'openbsd', 'android', 'haiku'].contains(host_machine.system()) -if have_lrt and not lrt.found() - lrt = cc.find_library('rt', required: true, static: is_static_build) -endif - -if host_machine.system() == 'darwin' - # On older Mac OS X versions (at least Lion and older), environ is only available when compiling an executable, - # but we will use is primarily in shared libs. Unfortunately, cc.links() only checks for executable compiling, - # so this does not help us. - # But in fact, even the environ(7) man page on macOS 11.6 still claims that environ is not avaliable for - # shared libs (even though it seems to work) and suggests using _NSGetEnviron(), so let's just do this always. - have_environ = false -else - code = '#include \nextern char **environ;\nint main() { char **env = environ; }' - have_environ = cc.links(code, name: 'have extern char **environ') -endif -userconf.set10('HAVE_ENVIRON', have_environ) -message('HAVE_ENVIRON: @0@'.format(have_environ)) - -code = '#if __is_target_os(ios)\nint x = 0;\n#endif\nint main() { x++; }' -is_ios = cc.links(code, name: 'target is ios') -userconf.set10('IS_IOS', is_ios) -message('IS_IOS: @0@'.format(is_ios)) - -foreach item : [ - ['arc4random_uniform', '#include ', []], - ['explicit_bzero', '#include ', []], - ['explicit_memset', '#include ', []], - ['clock_nanosleep', '#include ', []], - ['clock_gettime', '#include ', [lrt]], - ['sigaction', '#include ', []], - ['pipe', '#include ', []], - ['execv', '#include ', []], - ['execve', '#include ', []], - ['execvp', '#include ', []], - ['execl', '#include ', []], - ['system', '#include ', []], - ['realpath', '#include ', []], - ['fork', '#include ', []], - ['nice', '#include ', []], - ['copyfile', '#include ', []], - ['strlcpy', '#include ', []], - ['strnlen', '#include ', []], - ['shm_open', '#include ', [lrt]], - ['openpty', '', [utl]], - ['forkpty', '', [utl]], - ['login_tty', '', [utl]], - ['pipe2', '#define _GNU_SOURCE\n#include \n#include ', []], - # copy_file_range for now disable on freebsd as it s not reliable even for small chunks - ['copy_file_range', '#ifdef __linux__\n#define _GNU_SOURCE\n#include \n#endif', []], - ['backtrace', '', []], - ] - func = item[0] - ok = cc.has_function(func, prefix: item[1], dependencies: item[2]) - userconf.set10('HAVE_@0@'.format(func.to_upper()), ok) -endforeach - -foreach item : [ - ['linux/ashmem.h', '', []], - ['sys/shm.h', '', []], - ['sys/ipc.h', '', []], - ['sys/mman.h', '', []], - ['inttypes.h', '', []], - ] - hdr = item[0] - ok = cc.has_header(hdr, prefix: item[1], dependencies: item[2]) - userconf.set10('HAVE_HEADER_@0@'.format(hdr.underscorify().to_upper()), ok) -endforeach +ccs = [[cc, userconf, host_machine, false]] +if meson.is_cross_build() + userconf_native = configuration_data() + ccs += [[cc_native, userconf_native, build_machine, true]] +endif +userconfs = [] + +foreach it : ccs + it_cc = it[0] + it_userconf = it[1] + it_machine = it[2] + it_native = it[3] + + # required for linux + it_utl = it_cc.find_library('util', required: false, static: is_static_build) + it_ldl = it_cc.find_library('dl', required: false, static: is_static_build) + it_th = dependency('threads', required: false, static: is_static_build, native: it_native) + have_pthread = it_th.found() and it_machine.system() != 'windows' + if it_machine.system() == 'sunos' + # workaround for Solaris until https://github.com/mesonbuild/meson/issues/4328 is fixed + it_mth = declare_dependency(link_args: '-lm', native: it_native) + else + it_mth = it_cc.find_library('m', required: false, static: is_static_build) + endif + it_lrt = dependency('', required: false, native: it_native) + if not it_cc.has_function('clock_gettime', prefix: '#include ') and it_cc.has_header_symbol('features.h', '__GLIBC__') + it_lrt = it_cc.find_library('rt', required: true, static: is_static_build) + endif + have_lrt = not ['windows', 'darwin', 'openbsd', 'android', 'haiku'].contains(it_machine.system()) + if have_lrt and not it_lrt.found() + it_lrt = it_cc.find_library('rt', required: true, static: is_static_build) + endif -if userconf.get('HAVE_PIPE2') == 1 - add_project_arguments('-D_GNU_SOURCE', language: ['c', 'cpp']) -endif + it_userconf.set('RZ_CHECKS_LEVEL', checks_level) + it_userconf.set10('IS_PORTABLE', get_option('portable')) + it_userconf.set10('HAVE_LIB_MAGIC', sys_magic.found()) + it_userconf.set10('USE_LIB_MAGIC', sys_magic.found()) + it_userconf.set10('HAVE_LIB_XXHASH', xxhash_dep.found()) + it_userconf.set10('USE_LIB_XXHASH', xxhash_dep.found()) + it_userconf.set10('DEBUGGER', has_debugger) + it_userconf.set('PREFIX', rizin_prefix) + it_userconf.set('BINDIR', rizin_bindir) + it_userconf.set('BINDIR_DEPTH', bindir_depth) + it_userconf.set('LIBDIR', rizin_libdir) + it_userconf.set('INCLUDEDIR', rizin_incdir) + it_userconf.set('DATADIR_RZ', rizin_datdir_rz) + is_ppc = it_machine.cpu_family() == 'ppc' or it_machine.cpu_family() == 'ppc64' + it_userconf.set10('HAVE_JEMALLOC', it_machine.system() != 'windows' and (it_machine.system() != 'darwin' or not is_ppc)) + it_userconf.set('DATADIR', rizin_datdir) + it_userconf.set('WWWROOT', rizin_wwwroot) + it_userconf.set('SDB', rizin_sdb) + it_userconf.set('SIGDB', rizin_sigdb) + it_userconf.set('THEMES', rizin_themes) + it_userconf.set('FORTUNES', rizin_fortunes) + it_userconf.set('FLAGS', rizin_flags) + it_userconf.set('HUD', rizin_hud) + it_userconf.set('PLUGINS', rizin_plugins) + it_userconf.set('BINDINGS', rizin_bindings) + it_userconf.set10('HAVE_OPENSSL', sys_openssl.found()) + it_userconf.set10('HAVE_LIBUV', libuv_dep.found()) + it_userconf.set10('WANT_DYLINK', true) + it_userconf.set10('HAVE_PTRACE', have_ptrace) + it_userconf.set10('USE_PTRACE_WRAP', use_ptrace_wrap) + it_userconf.set10('WITH_GPL', get_option('use_gpl')) + it_userconf.set10('WITH_SWIFT_DEMANGLER', get_option('use_swift_demangler')) + it_userconf.set10('RZ_BUILD_DEBUG', get_option('buildtype').startswith('debug')) + ok = it_cc.has_header_symbol('sys/personality.h', 'ADDR_NO_RANDOMIZE') + it_userconf.set10('HAVE_DECL_ADDR_NO_RANDOMIZE', ok) + ok = it_cc.has_header_symbol('sys/procctl.h', 'PROC_ASLR_CTL') + it_userconf.set10('HAVE_DECL_PROCCTL_ASLR_CTL', ok) + it_userconf.set10('HAVE_THREADS', it_th.found()) + it_userconf.set10('HAVE_PTHREAD', have_pthread) + it_userconf.set10('HAVE_LZMA', get_option('use_lzma')) + it_userconf.set10('HAVE_ZLIB', get_option('use_zlib')) + + if it_machine.system() == 'freebsd' or it_machine.system() == 'dragonfly' + add_project_link_arguments('-Wl,--unresolved-symbols,ignore-in-object-files', language: 'c', native: it_native) + add_project_link_arguments('-Wl,--allow-shlib-undefined', language: 'c', native: it_native) + endif + if it_machine.system() == 'darwin' + # On older Mac OS X versions (at least Lion and older), environ is only available when compiling an executable, + # but we will use is primarily in shared libs. Unfortunately, it_cc.links() only checks for executable compiling, + # so this does not help us. + # But in fact, even the environ(7) man page on macOS 11.6 still claims that environ is not avaliable for + # shared libs (even though it seems to work) and suggests using _NSGetEnviron(), so let's just do this always. + have_environ = false + else + code = '#include \nextern char **environ;\nint main() { char **env = environ; }' + have_environ = it_cc.links(code, name: 'have extern char **environ') + endif + it_userconf.set10('HAVE_ENVIRON', have_environ) + message('HAVE_ENVIRON: @0@'.format(have_environ)) + + code = '#if __is_target_os(ios)\nint x = 0;\n#endif\nint main() { x++; }' + is_ios = it_cc.links(code, name: 'target is ios') + it_userconf.set10('IS_IOS', is_ios) + message('IS_IOS: @0@'.format(is_ios)) + + foreach item : [ + ['arc4random_uniform', '#include ', []], + ['explicit_bzero', '#include ', []], + ['explicit_memset', '#include ', []], + ['clock_nanosleep', '#include ', []], + ['clock_gettime', '#include ', [it_lrt]], + ['sigaction', '#include ', []], + ['pipe', '#include ', []], + ['execv', '#include ', []], + ['execve', '#include ', []], + ['execvp', '#include ', []], + ['execl', '#include ', []], + ['system', '#include ', []], + ['realpath', '#include ', []], + ['fork', '#include ', []], + ['nice', '#include ', []], + ['copyfile', '#include ', []], + ['strlcpy', '#include ', []], + ['strnlen', '#include ', []], + ['shm_open', '#include ', [it_lrt]], + ['openpty', '', [it_utl]], + ['forkpty', '', [it_utl]], + ['login_tty', '', [it_utl]], + ['pipe2', '#define _GNU_SOURCE\n#include \n#include ', []], + # copy_file_range for now disable on freebsd as it s not reliable even for small chunks + ['copy_file_range', '#ifdef __linux__\n#define _GNU_SOURCE\n#include \n#endif', []], + ['backtrace', '', []], + ] + func = item[0] + ok = it_cc.has_function(func, prefix: item[1], dependencies: item[2]) + it_userconf.set10('HAVE_@0@'.format(func.to_upper()), ok) + endforeach + + foreach item : [ + ['linux/ashmem.h', '', []], + ['sys/shm.h', '', []], + ['sys/ipc.h', '', []], + ['sys/mman.h', '', []], + ['inttypes.h', '', []], + ] + hdr = item[0] + ok = it_cc.has_header(hdr, prefix: item[1], dependencies: item[2]) + it_userconf.set10('HAVE_HEADER_@0@'.format(hdr.underscorify().to_upper()), ok) + endforeach + + if it_userconf.get('HAVE_PIPE2') == 1 + add_project_arguments('-D_GNU_SOURCE', language: ['c', 'cpp'], native: it_native) + endif + userconfs += [[it_userconf, it_lrt, it_utl, it_ldl, it_mth, it_th]] +endforeach +userconf = userconfs[0][0] +lrt = userconfs[0][1] +utl = userconfs[0][2] +ldl = userconfs[0][3] +mth = userconfs[0][4] +th = userconfs[0][5] +if meson.is_cross_build() + userconf_native = userconfs[1][0] + lrt_native = userconfs[1][1] + utl_native = userconfs[1][2] + ldl_native = userconfs[1][3] + mth_native = userconfs[1][4] + th_native = userconfs[1][5] +endif + +rz_userconf_h_in = files('librz/include/rz_userconf.h.in') rz_userconf_h = configure_file( - input: 'librz/include/rz_userconf.h.in', + input: rz_userconf_h_in, output: 'rz_userconf.h', configuration: userconf, install_dir: rizin_incdir @@ -606,23 +636,6 @@ endif yxml_proj = subproject('yxml', default_options: ['default_library=static']) yxml_dep = yxml_proj.get_variable('yxml_dep') -# handle sdb dependency -r = run_command(py3_exe, check_meson_subproject_py, 'sdb', check: false) -if r.returncode() == 1 and get_option('subprojects_check') - error('Subprojects are not updated. Please run `git clean -dxff subprojects/` to delete all local subprojects directories. If you want to compile against current subprojects then set option `subprojects_check=false`.') -endif - -sdb_proj = subproject('sdb', default_options: ['default_library=static', 'sdb_includedir=' + rizin_incdir]) -sdb_dep = sdb_proj.get_variable('sdb_whole_dep') -sdb_exe = sdb_proj.get_variable('sdb_native_exe') - -sdb_gen_cmd = [ - sdb_exe, - '@OUTPUT@', - '==', - '@INPUT@' -] - use_rpath = false use_rpath_absolute = false if host_machine.system() != 'windows' and get_option('default_library') == 'shared' diff --git a/test/unit/meson.build b/test/unit/meson.build index 03d7f28eded..857ec562d64 100644 --- a/test/unit/meson.build +++ b/test/unit/meson.build @@ -73,6 +73,12 @@ if get_option('enable_tests') 'reg', 'run', 'rz_test', + 'sdb_array', + 'sdb_diff', + 'sdb_hash', + 'sdb_ls', + 'sdb_sdb', + 'sdb_util', 'serialize_analysis', 'serialize_config', 'serialize_debug', diff --git a/test/unit/test_sdb_array.c b/test/unit/test_sdb_array.c new file mode 100644 index 00000000000..4b94e491226 --- /dev/null +++ b/test/unit/test_sdb_array.c @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#include +#include "minunit.h" + +bool test_sdb_array_push_pop(void) { + Sdb *db = sdb_new(NULL, NULL, false); + char *p; + + sdb_array_push(db, "foo", "foo", 0); + sdb_array_push(db, "foo", "bar", 0); + sdb_array_push(db, "foo", "cow", 0); + + mu_assert_streq(sdb_const_get(db, "foo", 0), "cow,bar,foo", "Not all items found"); + + p = sdb_array_pop(db, "foo", NULL); + mu_assert_streq(p, "cow", "cow was not at the top"); + free(p); + + p = sdb_array_pop(db, "foo", NULL); + mu_assert_streq(p, "bar", "bar was not at the top"); + free(p); + + p = sdb_array_pop(db, "foo", NULL); + mu_assert_streq(p, "foo", "foo was not at the top"); + free(p); + + p = sdb_array_pop(db, "foo", NULL); + mu_assert_eq((int)(size_t)p, (int)(size_t)NULL, "there shouldn't be any element in the array"); + free(p); + + sdb_free(db); + mu_end; +} + +bool test_sdb_array_add_remove(void) { + Sdb *db = sdb_new(NULL, NULL, false); + sdb_array_add(db, "foo", "foo", 0); + sdb_array_add(db, "foo", "bar", 0); + sdb_array_add(db, "foo", "cow", 0); + + mu_assert_streq(sdb_const_get(db, "foo", 0), "foo,bar,cow", "Not all items found"); + + sdb_array_remove(db, "foo", "bar", 0); + mu_assert_streq(sdb_const_get(db, "foo", 0), "foo,cow", "bar was not deleted"); + sdb_array_remove(db, "foo", "nothing", 0); + mu_assert_streq(sdb_const_get(db, "foo", 0), "foo,cow", "nothing should be deleted"); + sdb_array_remove(db, "foo", "cow", 0); + sdb_array_remove(db, "foo", "foo", 0); + mu_assert_eq((int)(size_t)sdb_const_get(db, "foo", 0), (int)(size_t)NULL, "all elements should be deleted"); + + sdb_free(db); + mu_end; +} + +int all_tests() { + mu_run_test(test_sdb_array_push_pop); + mu_run_test(test_sdb_array_add_remove); + return tests_passed != tests_run; +} + +int main(int argc, char **argv) { + return all_tests(); +} diff --git a/test/unit/test_sdb_diff.c b/test/unit/test_sdb_diff.c new file mode 100644 index 00000000000..eec326dc1c7 --- /dev/null +++ b/test/unit/test_sdb_diff.c @@ -0,0 +1,258 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#include +#include "minunit.h" + +// #define SAVE_FILES + +static Sdb *test_sdb_new(const char *file) { +#ifdef SAVE_FILES + Sdb *r = sdb_new(NULL, file, 0); + // sdb_disk_create (r); +#else + Sdb *r = sdb_new0(); +#endif + sdb_set(r, "some", "stuff", 0); + sdb_set(r, "and", "even", 0); + sdb_set(r, "more", "stuff", 0); + + sdb_ns(r, "emptyns", true); + + Sdb *test_ns = sdb_ns(r, "test", true); + sdb_set(test_ns, "a", "123", 0); + sdb_set(test_ns, "b", "test", 0); + sdb_set(test_ns, "c", "hello", 0); + + Sdb *subspace_ns = sdb_ns(test_ns, "subspace", true); + sdb_set(subspace_ns, "some", "values", 0); + sdb_set(subspace_ns, "are", "saved", 0); + sdb_set(subspace_ns, "here", "lol", 0); + return r; +} + +static void test_sdb_free(Sdb *sdb) { + if (!sdb) { + return; + } +#ifdef SAVE_FILES + sdb_sync(sdb); // sdb_disk_finish (sdb); +#endif + sdb_free(sdb); +} + +typedef struct { + char buf[2048]; + size_t buf_len; +} Ctx; + +void diff_cb(const SdbDiff *diff, void *user) { + Ctx *ctx = user; + int r = sdb_diff_format(ctx->buf + ctx->buf_len, sizeof(ctx->buf) - ctx->buf_len, diff); + if (r >= 0) { + ctx->buf_len += r; + if (ctx->buf_len >= sizeof(ctx->buf)) { + ctx->buf_len = sizeof(ctx->buf) - 1; + } + if (ctx->buf_len < sizeof(ctx->buf) + 1) { + ctx->buf[ctx->buf_len++] = '\n'; + ctx->buf[ctx->buf_len] = '\0'; + } + } +} + +static bool diff_str(Sdb *a, Sdb *b, char **diff) { + Ctx ctx = { 0 }; + bool eq = sdb_diff(a, b, diff ? diff_cb : NULL, diff ? &ctx : NULL); + if (diff) { + *diff = strdup(ctx.buf); + } + return eq; +} + +bool test_sdb_diff_equal_empty() { + Sdb *a = sdb_new0(); + Sdb *b = sdb_new0(); + mu_assert("equal db (no diff)", diff_str(a, b, NULL)); + char *diff; + mu_assert("equal db (diff)", diff_str(a, b, &diff)); + mu_assert_streq(diff, "", "equal db diff"); + free(diff); + test_sdb_free(a); + test_sdb_free(b); + mu_end; +} + +bool test_sdb_diff_equal() { + Sdb *a = test_sdb_new("equal_a.sdb"); + Sdb *b = test_sdb_new("equal_b.sdb"); + mu_assert("equal db (no diff)", diff_str(a, b, NULL)); + char *diff; + mu_assert("equal db (diff)", diff_str(a, b, &diff)); + mu_assert_streq(diff, "", "equal db diff"); + free(diff); + test_sdb_free(a); + test_sdb_free(b); + mu_end; +} + +bool test_sdb_diff_ns_empty() { + Sdb *a = test_sdb_new("ns_empty_a.sdb"); + Sdb *b = test_sdb_new("ns_empty_b.sdb"); + sdb_ns_unset(b, "emptyns", NULL); + + mu_assert("empty ns removed (no diff)", !diff_str(a, b, NULL)); + char *diff; + mu_assert("empty ns removed (diff)", !diff_str(a, b, &diff)); + mu_assert_streq(diff, "-NS emptyns\n", "empty ns removed diff"); + free(diff); + + mu_assert("empty ns added (no diff)", !diff_str(b, a, NULL)); + mu_assert("empty ns added (diff)", !diff_str(b, a, &diff)); + mu_assert_streq(diff, "+NS emptyns\n", "empty ns added diff"); + free(diff); + + test_sdb_free(a); + test_sdb_free(b); + mu_end; +} + +bool test_sdb_diff_ns() { + Sdb *a = test_sdb_new("ns_a.sdb"); + Sdb *b = test_sdb_new("ns_b.sdb"); + sdb_ns_unset(b, "test", NULL); + + mu_assert("ns removed (no diff)", !diff_str(a, b, NULL)); + char *diff; + mu_assert("ns removed (diff)", !diff_str(a, b, &diff)); + mu_assert_streq(diff, + "-NS test\n" + "-NS test/subspace\n" + "- test/subspace/here=lol\n" + "- test/subspace/some=values\n" + "- test/subspace/are=saved\n" + "- test/a=123\n" + "- test/c=hello\n" + "- test/b=test\n", + "ns removed diff"); + free(diff); + + mu_assert("ns added (no diff)", !diff_str(b, a, NULL)); + mu_assert("ns added (diff)", !diff_str(b, a, &diff)); + mu_assert_streq(diff, + "+NS test\n" + "+NS test/subspace\n" + "+ test/subspace/here=lol\n" + "+ test/subspace/some=values\n" + "+ test/subspace/are=saved\n" + "+ test/a=123\n" + "+ test/c=hello\n" + "+ test/b=test\n", + "ns added diff"); + free(diff); + + test_sdb_free(a); + test_sdb_free(b); + mu_end; +} + +bool test_sdb_diff_ns_sub() { + Sdb *a = test_sdb_new("ns_sub_a.sdb"); + Sdb *b = test_sdb_new("ns_sub_b.sdb"); + sdb_ns_unset(sdb_ns(b, "test", 0), "subspace", NULL); + + mu_assert("sub ns removed (no diff)", !diff_str(a, b, NULL)); + char *diff; + mu_assert("sub ns removed (diff)", !diff_str(a, b, &diff)); + mu_assert_streq(diff, + "-NS test/subspace\n" + "- test/subspace/here=lol\n" + "- test/subspace/some=values\n" + "- test/subspace/are=saved\n", + "sub ns removed diff"); + free(diff); + + mu_assert("sub ns added (no diff)", !diff_str(b, a, NULL)); + mu_assert("sub ns added (diff)", !diff_str(b, a, &diff)); + mu_assert_streq(diff, + "+NS test/subspace\n" + "+ test/subspace/here=lol\n" + "+ test/subspace/some=values\n" + "+ test/subspace/are=saved\n", + "sub ns added diff"); + free(diff); + + test_sdb_free(a); + test_sdb_free(b); + mu_end; +} + +bool test_sdb_diff_kv() { + Sdb *a = test_sdb_new("kv_a.sdb"); + Sdb *b = test_sdb_new("kv_b.sdb"); + sdb_unset(b, "more", 0); + sdb_unset(sdb_ns(b, "test", false), "a", 0); + + mu_assert("kv removed (no diff)", !diff_str(a, b, NULL)); + char *diff; + mu_assert("kv removed (diff)", !diff_str(a, b, &diff)); + mu_assert_streq(diff, + "- test/a=123\n" + "- more=stuff\n", + "ns removed diff"); + free(diff); + + mu_assert("kv added (no diff)", !diff_str(b, a, NULL)); + mu_assert("kv added (diff)", !diff_str(b, a, &diff)); + mu_assert_streq(diff, + "+ test/a=123\n" + "+ more=stuff\n", + "ns added diff"); + free(diff); + + test_sdb_free(a); + test_sdb_free(b); + mu_end; +} + +bool test_sdb_diff_kv_value() { + Sdb *a = test_sdb_new("kv_value_a.sdb"); + Sdb *b = test_sdb_new("kv_value_b.sdb"); + sdb_set(b, "more", "cowbell", 0); + sdb_set(sdb_ns(b, "test", false), "a", "reaper", 0); + + mu_assert("kv value changed (no diff)", !diff_str(a, b, NULL)); + char *diff; + mu_assert("kv value changed (diff)", !diff_str(a, b, &diff)); + mu_assert_streq(diff, + "- test/a=123\n" + "+ test/a=reaper\n" + "- more=stuff\n" + "+ more=cowbell\n", + "ns value changed diff"); + free(diff); + + test_sdb_free(a); + test_sdb_free(b); + mu_end; +} + +int all_tests() { + mu_run_test(test_sdb_diff_equal_empty); + mu_run_test(test_sdb_diff_equal); + mu_run_test(test_sdb_diff_ns_empty); + mu_run_test(test_sdb_diff_ns); + mu_run_test(test_sdb_diff_ns_sub); + mu_run_test(test_sdb_diff_kv); + mu_run_test(test_sdb_diff_kv_value); + return tests_passed != tests_run; +} + +int main(int argc, char **argv) { + // Sdb *sdb = test_sdb_new(); + // sdb_query (sdb, "*"); + // sdb_query (sdb, "***"); + // test_sdb_free (sdb); + // return 0; + return all_tests(); +} diff --git a/test/unit/test_sdb_hash.c b/test/unit/test_sdb_hash.c new file mode 100644 index 00000000000..aa2ce048a46 --- /dev/null +++ b/test/unit/test_sdb_hash.c @@ -0,0 +1,564 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#include "minunit.h" +#include +#include +#include +#include +#include + +typedef struct _test_struct { + char *name; + int age; +} Person; + +bool test_ht_insert_lookup(void) { + HtPP *ht = sdb_ht_new(); + sdb_ht_insert(ht, "AAAA", "vAAAA"); + sdb_ht_insert(ht, "BBBB", "vBBBB"); + sdb_ht_insert(ht, "CCCC", "vCCCC"); + + mu_assert_streq(sdb_ht_find(ht, "BBBB", NULL), "vBBBB", "BBBB value wrong"); + mu_assert_streq(sdb_ht_find(ht, "AAAA", NULL), "vAAAA", "AAAA value wrong"); + mu_assert_streq(sdb_ht_find(ht, "CCCC", NULL), "vCCCC", "CCCC value wrong"); + + sdb_ht_free(ht); + mu_end; +} + +bool test_ht_update_lookup(void) { + HtPP *ht = sdb_ht_new(); + sdb_ht_insert(ht, "AAAA", "vAAAA"); + sdb_ht_insert(ht, "BBBB", "vBBBB"); + + // test update to add a new element + sdb_ht_update(ht, "CCCC", "vCCCC"); + mu_assert_streq(sdb_ht_find(ht, "CCCC", NULL), "vCCCC", "CCCC value wrong"); + + // test update to replace an existing element + sdb_ht_update(ht, "AAAA", "vDDDD"); + mu_assert_streq(sdb_ht_find(ht, "AAAA", NULL), "vDDDD", "DDDD value wrong"); + + sdb_ht_free(ht); + mu_end; +} + +bool test_ht_delete(void) { + HtPP *ht = sdb_ht_new(); + mu_assert("nothing should be deleted", !sdb_ht_delete(ht, "non existing")); + + sdb_ht_insert(ht, "AAAA", "vAAAA"); + mu_assert("AAAA should be deleted", sdb_ht_delete(ht, "AAAA")); + mu_assert("AAAA still there", !sdb_ht_find(ht, "AAAA", NULL)); + + sdb_ht_free(ht); + mu_end; +} + +bool test_ht_insert_kvp(void) { + HtPP *ht = sdb_ht_new(); + SdbKv *kv = sdbkv_new("AAAA", "vAAAA"); + mu_assert("AAAA shouldn't exist", !sdb_ht_find_kvp(ht, "AAAA", NULL)); + sdb_ht_insert_kvp(ht, kv, false); + free(kv); + + mu_assert("AAAA should exist", sdb_ht_find_kvp(ht, "AAAA", NULL)); + SdbKv *kv2 = sdbkv_new("AAAA", "vNEWAAAA"); + mu_assert("AAAA shouldn't be replaced", !sdb_ht_insert_kvp(ht, kv2, false)); + mu_assert("AAAA should be replaced", sdb_ht_insert_kvp(ht, kv2, true)); + free(kv2); + + SdbKv *foundkv = sdb_ht_find_kvp(ht, "AAAA", NULL); + mu_assert_streq(foundkv->base.value, "vNEWAAAA", "vNEWAAAA should be there"); + + sdb_ht_free(ht); + mu_end; +} + +ut32 create_collision(const void *key) { + return 10; +} + +bool test_ht_insert_collision(void) { + HtPP *ht = sdb_ht_new(); + ht->opt.hashfn = create_collision; + ht_pp_insert(ht, "AAAA", "vAAAA"); + mu_assert_streq(sdb_ht_find(ht, "AAAA", NULL), "vAAAA", "AAAA should be there"); + ht_pp_insert(ht, "BBBB", "vBBBB"); + mu_assert_streq(sdb_ht_find(ht, "AAAA", NULL), "vAAAA", "AAAA should still be there"); + mu_assert_streq(sdb_ht_find(ht, "BBBB", NULL), "vBBBB", "BBBB should be there"); + ht_pp_insert(ht, "CCCC", "vBBBB"); + mu_assert_streq(sdb_ht_find(ht, "CCCC", NULL), "vBBBB", "CCCC should be there"); + + sdb_ht_free(ht); + mu_end; +} + +ut32 key2hash(const void *key) { + return atoi(key); +} + +bool test_ht_grow(void) { + HtPP *ht = sdb_ht_new(); + char str[15], vstr[15]; + char buf[100]; + int i; + + ht->opt.hashfn = key2hash; + for (i = 0; i < 20000; ++i) { + snprintf(str, 15, "%d", i); + snprintf(vstr, 15, "v%d", i); + sdb_ht_insert(ht, str, vstr); + } + + for (i = 0; i < 20000; ++i) { + snprintf(str, 15, "%d", i); + snprintf(vstr, 15, "v%d", i); + char *v = sdb_ht_find(ht, str, NULL); + snprintf(buf, 100, "%s/%s should be there", str, vstr); + mu_assert(buf, v); + snprintf(buf, 100, "%s/%s should be right", str, vstr); + mu_assert_streq(v, vstr, buf); + } + + sdb_ht_free(ht); + mu_end; +} + +bool test_ht_kvp(void) { + HtPP *ht = sdb_ht_new(); + SdbKv *kvp = sdbkv_new("AAAA", "vAAAA"); + + mu_assert_eq(kvp->base.key_len, 4, "key_len should be 4"); + mu_assert_eq(kvp->base.value_len, 5, "value_len should be 5"); + mu_assert("kvp should be inserted", sdb_ht_insert_kvp(ht, kvp, false)); + free(kvp); + + kvp = sdb_ht_find_kvp(ht, "AAAA", NULL); + mu_assert_eq(kvp->base.key_len, 4, "key_len should be 4 after kvp_insert"); + mu_assert_eq(kvp->base.value_len, 5, "value_len should be 5 after kvp_insert"); + + sdb_ht_insert(ht, "BBBB", "vBBBB"); + kvp = sdb_ht_find_kvp(ht, "BBBB", NULL); + mu_assert_eq(kvp->base.key_len, 4, "key_len should be 4 after insert"); + mu_assert_eq(kvp->base.value_len, 5, "value_len should be 5 after insert"); + + sdb_ht_free(ht); + mu_end; +} + +Person *duplicate_person(Person *p) { + Person *c = malloc(sizeof(Person)); + c->name = strdup(p->name); + c->age = p->age; + return c; +} + +void free_kv(HtPPKv *kv) { + free(kv->key); + Person *p = kv->value; + free(p->name); + free(p); +} + +size_t calcSizePerson(void *c) { + Person *p = c; + return sizeof(*p); +} +bool test_ht_general(void) { + int retval = MU_PASSED; + bool found = false; + Person *p, *person1 = malloc(sizeof(Person)); + if (!person1) { + mu_cleanup_fail(err_malloc, "person1 malloc"); + } + person1->name = strdup("radare"); + person1->age = 10; + + Person *person2 = malloc(sizeof(Person)); + if (!person2) { + mu_cleanup_fail(err_free_person1, "person2 malloc"); + } + person2->name = strdup("pancake"); + person2->age = 9000; + + HtPP *ht = ht_pp_new((HtPPDupValue)duplicate_person, free_kv, (HtPPCalcSizeV)calcSizePerson); + if (!ht) { + mu_cleanup_fail(err_free_persons, "ht alloc"); + } + ht_pp_insert(ht, "radare", (void *)person1); + ht_pp_insert(ht, "pancake", (void *)person2); + p = ht_pp_find(ht, "radare", &found); + mu_assert("radare not found", found); + mu_assert_streq(p->name, "radare", "wrong person"); + mu_assert_eq(p->age, 10, "wrong radare age"); + + p = ht_pp_find(ht, "pancake", &found); + mu_assert("radare not found", found); + mu_assert_streq(p->name, "pancake", "wrong person"); + mu_assert_eq(p->age, 9000, "wrong pancake age"); + + (void)ht_pp_find(ht, "not", &found); + mu_assert("found but it should not exists", !found); + + ht_pp_delete(ht, "pancake"); + p = ht_pp_find(ht, "pancake", &found); + mu_assert("pancake was deleted", !found); + + ht_pp_insert(ht, "pancake", (void *)person2); + ht_pp_delete(ht, "radare"); + ht_pp_update(ht, "pancake", (void *)person1); + p = ht_pp_find(ht, "pancake", &found); + + mu_assert("pancake was updated", found); + mu_assert_streq(p->name, "radare", "wrong person"); + mu_assert_eq(p->age, 10, "wrong age"); + + ht_pp_free(ht); +err_free_persons: + free(person2->name); + free(person2); +err_free_person1: + free(person1->name); + free(person1); +err_malloc: + mu_cleanup_end; +} + +static void free_key_value(HtPPKv *kv) { + free(kv->key); + free(kv->value); +} + +bool should_not_be_caled(void *user, const char *k, void *v) { + mu_fail("this function should not be called"); + return false; +} + +bool test_empty_ht(void) { + HtPP *ht = ht_pp_new0(); + ht_pp_foreach(ht, (HtPPForeachCallback)should_not_be_caled, NULL); + void *r = ht_pp_find(ht, "key1", NULL); + mu_assert_null(r, "key1 should not be present"); + ht_pp_free(ht); + mu_end; +} + +bool test_insert(void) { + HtPP *ht = ht_pp_new0(); + void *r; + bool res; + bool found; + + res = ht_pp_insert(ht, "key1", "value1"); + mu_assert("key1 should be a new element", res); + r = ht_pp_find(ht, "key1", &found); + mu_assert("found should be true", found); + mu_assert_streq(r, "value1", "value1 should be retrieved"); + + res = ht_pp_insert(ht, "key1", "value2"); + mu_assert("key1 should be an already existing element", !res); + r = ht_pp_find(ht, "key1", &found); + mu_assert_streq(r, "value1", "value1 should be retrieved"); + mu_assert("found should be true", found); + + r = ht_pp_find(ht, "key2", &found); + mu_assert_null(r, "key2 should not be present"); + mu_assert("found for key2 should be false", !found); + + ht_pp_free(ht); + mu_end; +} + +bool test_update(void) { + HtPP *ht = ht_pp_new0(); + bool found; + + ht_pp_insert(ht, "key1", "value1"); + ht_pp_update(ht, "key1", "value2"); + void *r = ht_pp_find(ht, "key1", &found); + mu_assert_streq(r, "value2", "value2 should be retrieved"); + mu_assert("found should be true", found); + ht_pp_free(ht); + mu_end; +} + +bool test_delete(void) { + HtPP *ht = ht_pp_new0(); + bool found; + + ht_pp_insert(ht, "key1", "value1"); + ht_pp_delete(ht, "key1"); + void *r = ht_pp_find(ht, "key1", &found); + mu_assert_null(r, "key1 should not be found"); + mu_assert("found should be false", !found); + ht_pp_free(ht); + mu_end; +} + +static bool grow_1_found[3]; +static bool grow_1_foreach(void *user, const char *k, int v) { + grow_1_found[v] = true; + return true; +} + +bool test_grow_1(void) { + HtPP *ht = ht_pp_new0(); + int i; + + for (i = 0; i < 3; ++i) { + grow_1_found[i] = false; + } + + ht_pp_insert(ht, "key0", (void *)0); + ht_pp_insert(ht, "key1", (void *)1); + ht_pp_insert(ht, "key2", (void *)2); + + ht_pp_foreach(ht, (HtPPForeachCallback)grow_1_foreach, NULL); + for (i = 0; i < 3; ++i) { + if (!grow_1_found[i]) { + fprintf(stderr, "i = %d\n", i); + mu_fail("An element has not been traversed"); + } + } + + ht_pp_free(ht); + mu_end; +} + +bool test_grow_2(void) { + HtPP *ht = ht_pp_new((HtPPDupValue)strdup, (HtPPKvFreeFunc)free_key_value, NULL); + char *r; + bool found; + int i; + + for (i = 0; i < 3000; ++i) { + char buf[20], buf2[20]; + snprintf(buf, 20, "key%d", i); + snprintf(buf2, 20, "value%d", i); + ht_pp_insert(ht, buf, buf2); + } + + r = ht_pp_find(ht, "key1", &found); + mu_assert_streq(r, "value1", "value1 should be retrieved"); + mu_assert("found should be true", found); + + r = ht_pp_find(ht, "key2000", &found); + mu_assert_streq(r, "value2000", "value2000 should be retrieved"); + mu_assert("found should be true", found); + + r = ht_pp_find(ht, "key4000", &found); + mu_assert_null(r, "key4000 should not be there"); + mu_assert("found should be false", !found); + + ht_pp_free(ht); + mu_end; +} + +bool test_grow_3(void) { + HtPP *ht = ht_pp_new((HtPPDupValue)strdup, (HtPPKvFreeFunc)free_key_value, NULL); + char *r; + bool found; + int i; + + for (i = 0; i < 3000; ++i) { + char buf[20], buf2[20]; + snprintf(buf, 20, "key%d", i); + snprintf(buf2, 20, "value%d", i); + ht_pp_insert(ht, buf, buf2); + } + + for (i = 0; i < 3000; i += 3) { + char buf[20]; + snprintf(buf, 20, "key%d", i); + ht_pp_delete(ht, buf); + } + + r = ht_pp_find(ht, "key1", &found); + mu_assert_streq(r, "value1", "value1 should be retrieved"); + mu_assert("found should be true", found); + + r = ht_pp_find(ht, "key2000", &found); + mu_assert_streq(r, "value2000", "value2000 should be retrieved"); + mu_assert("found should be true", found); + + r = ht_pp_find(ht, "key4000", &found); + mu_assert_null(r, "key4000 should not be there"); + mu_assert("found should be false", !found); + + r = ht_pp_find(ht, "key0", &found); + mu_assert_null(r, "key0 should not be there"); + mu_assert("found should be false", !found); + + for (i = 1; i < 3000; i += 3) { + char buf[20]; + snprintf(buf, 20, "key%d", i); + ht_pp_delete(ht, buf); + } + + r = ht_pp_find(ht, "key1", &found); + mu_assert_null(r, "key1 should not be there"); + mu_assert("found should be false", !found); + + ht_pp_free(ht); + mu_end; +} + +bool test_grow_4(void) { + HtPP *ht = ht_pp_new(NULL, (HtPPKvFreeFunc)free_key_value, NULL); + char *r; + bool found; + int i; + + for (i = 0; i < 3000; ++i) { + char buf[20], *buf2; + snprintf(buf, 20, "key%d", i); + buf2 = malloc(20); + snprintf(buf2, 20, "value%d", i); + ht_pp_insert(ht, buf, buf2); + } + + r = ht_pp_find(ht, "key1", &found); + mu_assert_streq(r, "value1", "value1 should be retrieved"); + mu_assert("found should be true", found); + + r = ht_pp_find(ht, "key2000", &found); + mu_assert_streq(r, "value2000", "value2000 should be retrieved"); + mu_assert("found should be true", found); + + for (i = 0; i < 3000; i += 3) { + char buf[20]; + snprintf(buf, 20, "key%d", i); + ht_pp_delete(ht, buf); + } + + r = ht_pp_find(ht, "key2000", &found); + mu_assert_streq(r, "value2000", "value2000 should be retrieved"); + mu_assert("found should be true", found); + + r = ht_pp_find(ht, "key0", &found); + mu_assert_null(r, "key0 should not be there"); + mu_assert("found should be false", !found); + + for (i = 1; i < 3000; i += 3) { + char buf[20]; + snprintf(buf, 20, "key%d", i); + ht_pp_delete(ht, buf); + } + + r = ht_pp_find(ht, "key1", &found); + mu_assert_null(r, "key1 should not be there"); + mu_assert("found should be false", !found); + + ht_pp_free(ht); + mu_end; +} + +bool foreach_delete_cb(void *user, const ut64 key, const void *v) { + HtUP *ht = (HtUP *)user; + + ht_up_delete(ht, key); + return true; +} + +static void free_up_value(HtUPKv *kv) { + free(kv->value); +} + +bool test_foreach_delete(void) { + HtUP *ht = ht_up_new((HtUPDupValue)strdup, free_up_value, NULL); + + // create a collision + ht_up_insert(ht, 0, "value1"); + ht_up_insert(ht, ht->size, "value2"); + ht_up_insert(ht, ht->size * 2, "value3"); + ht_up_insert(ht, ht->size * 3, "value4"); + + ht_up_foreach(ht, foreach_delete_cb, ht); + ht_up_foreach(ht, (HtUPForeachCallback)should_not_be_caled, NULL); + + ht_up_free(ht); + mu_end; +} + +bool test_update_key(void) { + bool res; + HtUP *ht = ht_up_new((HtUPDupValue)strdup, free_up_value, NULL); + + // create a collision + ht_up_insert(ht, 0, "value1"); + ht_up_insert(ht, 0xdeadbeef, "value2"); + ht_up_insert(ht, 0xcafebabe, "value3"); + + res = ht_up_update_key(ht, 0xcafebabe, 0x10000); + mu_assert("cafebabe should be updated", res); + res = ht_up_update_key(ht, 0xdeadbeef, 0x10000); + mu_assert("deadbeef should NOT be updated, because there's already an element at 0x10000", !res); + + const char *v = ht_up_find(ht, 0x10000, NULL); + mu_assert_streq(v, "value3", "value3 should be at 0x10000"); + v = ht_up_find(ht, 0xdeadbeef, NULL); + mu_assert_streq(v, "value2", "value2 should remain at 0xdeadbeef"); + + ht_up_free(ht); + mu_end; +} + +bool test_ht_pu_ops(void) { + bool res; + ut64 val; + HtPU *ht = ht_pu_new0(); + + ht_pu_insert(ht, "key1", 0xcafebabe); + val = ht_pu_find(ht, "key1", &res); + mu_assert_eq(val, 0xcafebabe, "0xcafebabe should be retrieved"); + mu_assert("found should be true", res); + + res = ht_pu_insert(ht, "key1", 0xdeadbeefdeadbeef); + mu_assert("key1 should be an already existing element", !res); + val = ht_pu_find(ht, "key1", &res); + mu_assert_eq(val, 0xcafebabe, "0xcafebabe should still be retrieved"); + + res = ht_pu_update(ht, "key1", 0xdeadbeefdeadbeef); + mu_assert("key1 should be updated", res); + val = ht_pu_find(ht, "key1", &res); + mu_assert_eq(val, 0xdeadbeefdeadbeef, "0xdeadbeefdeadbeef should be retrieved"); + mu_assert("found should be true", res); + + res = ht_pu_delete(ht, "key1"); + mu_assert("key1 should be deleted", res); + val = ht_pu_find(ht, "key1", &res); + mu_assert_eq(val, 0, "0 should be retrieved"); + mu_assert("found should be false", !res); + + ht_pu_free(ht); + mu_end; +} + +int all_tests() { + mu_run_test(test_ht_insert_lookup); + mu_run_test(test_ht_update_lookup); + mu_run_test(test_ht_delete); + mu_run_test(test_ht_insert_kvp); + mu_run_test(test_ht_insert_collision); + mu_run_test(test_ht_grow); + mu_run_test(test_ht_kvp); + mu_run_test(test_ht_general); + mu_run_test(test_empty_ht); + mu_run_test(test_insert); + mu_run_test(test_update); + mu_run_test(test_delete); + mu_run_test(test_grow_1); + mu_run_test(test_grow_2); + mu_run_test(test_grow_3); + mu_run_test(test_grow_4); + mu_run_test(test_foreach_delete); + mu_run_test(test_update_key); + mu_run_test(test_ht_pu_ops); + return tests_passed != tests_run; +} + +int main(int argc, char **argv) { + return all_tests(); +} diff --git a/test/unit/test_sdb_ls.c b/test/unit/test_sdb_ls.c new file mode 100644 index 00000000000..daf16e5e23c --- /dev/null +++ b/test/unit/test_sdb_ls.c @@ -0,0 +1,454 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#include +#include "minunit.h" +#define BUF_LENGTH 100 + +#define R_ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +bool test_r_list_size(void) { + // Test that r_list adding and deleting works correctly. + int i; + SdbList *list = ls_new(); + intptr_t test = 0x101010; + // Add 100 items. + for (i = 0; i < 100; ++i) { + ls_append(list, (void *)test); + mu_assert_eq((int)ls_length(list), i + 1, "ls_length failed on append"); + } + // Delete 50 of them. + for (i = 0; i < 50; ++i) { + (void)ls_pop(list); + mu_assert_eq(99 - i, (int)ls_length(list), "ls_length failed on pop"); + } + // Purge the list. + ls_destroy(list); + mu_assert_eq(0, (int)ls_length(list), "ls_length failed on purged list"); + ls_free(list); + mu_end; +} + +bool test_r_list_values(void) { + SdbList *list = ls_new(); + intptr_t test1 = 0x12345; + intptr_t test2 = 0x88888; + ls_append(list, (void *)test1); + ls_append(list, (void *)test2); + int top1 = (intptr_t)ls_pop(list); + int top2 = (intptr_t)ls_pop(list); + mu_assert_eq(top1, 0x88888, "first value not 0x88888"); + mu_assert_eq(top2, 0x12345, "first value not 0x12345"); + ls_free(list); + mu_end; +} + +bool test_ls_join(void) { + SdbList *list1 = ls_new(); + SdbList *list2 = ls_new(); + intptr_t test1 = 0x12345; + intptr_t test2 = 0x88888; + ls_append(list1, (void *)test1); + ls_append(list2, (void *)test2); + int joined = ls_join(list1, list2); + mu_assert_eq(joined, 1, "ls_join of two lists"); + mu_assert_eq((int)ls_length(list1), 2, "ls_join two single element lists result length is 1"); + ls_free(list1); + ls_free(list2); + mu_end; +} + +bool test_ls_free(void) { + SdbList *list = ls_newf((void *)0x9999); + mu_assert_eq((int)(intptr_t)list->free, 0x9999, "ls_newf function gets set properly"); + ls_free(list); + mu_end; +} + +bool test_ls_del_n(void) { + SdbList *list = ls_new(); + intptr_t test1 = 0x12345; + intptr_t test2 = 0x88888; + ls_append(list, (void *)test1); + ls_append(list, (void *)test2); + mu_assert_eq((int)ls_length(list), 2, + "list is of length 2 when adding 2 values"); + ls_del_n(list, 0); + int top1 = (intptr_t)ls_pop(list); + mu_assert_eq(top1, 0x88888, + "error, first value not 0x88888"); + ls_free(list); + mu_end; +} + +bool test_r_list_sort(void) { + SdbList *list = ls_new(); + char *test1 = "AAAA"; + char *test2 = "BBBB"; + char *test3 = "CCCC"; + // Put in not sorted order. + ls_append(list, (void *)test1); + ls_append(list, (void *)test3); + ls_append(list, (void *)test2); + // Sort. + ls_sort(list, (SdbListComparator)strcmp); + // Check that the list is actually sorted. + mu_assert_streq((char *)list->head->data, "AAAA", "first value in sorted list"); + mu_assert_streq((char *)list->head->n->data, "BBBB", "second value in sorted list"); + mu_assert_streq((char *)list->head->n->n->data, "CCCC", "third value in sorted list"); + ls_free(list); + mu_end; +} + +bool test_r_list_sort2(void) { + SdbList *list = ls_new(); + char *test1 = "AAAA"; + char *test2 = "BBBB"; + char *test3 = "CCCC"; + // Put in not sorted order. + ls_append(list, (void *)test3); + ls_append(list, (void *)test2); + ls_append(list, (void *)test1); + // Sort. + ls_merge_sort(list, (SdbListComparator)strcmp); + // Check that the list is actually sorted. + mu_assert_streq((char *)list->head->data, "AAAA", "first value in sorted list"); + mu_assert_streq((char *)list->head->n->data, "BBBB", "second value in sorted list"); + mu_assert_streq((char *)list->head->n->n->data, "CCCC", "third value in sorted list"); + ls_free(list); + mu_end; +} + +static int cmp_range(const void *a, const void *b) { + int ra = *(int *)a; + int rb = *(int *)b; + return ra - rb; +} + +bool test_r_list_sort3(void) { + SdbList *list = ls_new(); + int test1 = 33508; + int test2 = 33480; + int test3 = 33964; + // Put in not sorted order. + ls_append(list, (void *)&test1); + ls_append(list, (void *)&test3); + ls_append(list, (void *)&test2); + // Sort. + ls_merge_sort(list, (SdbListComparator)cmp_range); + // Check that the list is actually sorted. + mu_assert_eq(*(int *)list->head->data, 33480, "first value in sorted list"); + mu_assert_eq(*(int *)list->head->n->data, 33508, "second value in sorted list"); + mu_assert_eq(*(int *)list->head->n->n->data, 33964, "third value in sorted list"); + ls_free(list); + mu_end; +} + +bool test_ls_length(void) { + SdbList *list = ls_new(); + SdbList *list2 = ls_new(); + SdbListIter *iter; + int count = 0; + int test1 = 33508; + int test2 = 33480; + int test3 = 33964; + // Put in not sorted order. + ls_append(list, (void *)&test1); + ls_append(list, (void *)&test3); + ls_append(list, (void *)&test2); + iter = list->head; + while (iter) { + count++; + iter = iter->n; + } + mu_assert_eq((int)list->length, 3, "First length check"); + + ls_delete_data(list, (void *)&test1); + mu_assert_eq((int)list->length, 2, "Second length check"); + + ls_append(list, (void *)&test1); + mu_assert_eq((int)list->length, 3, "Third length check"); + + ls_pop(list); + mu_assert_eq((int)list->length, 2, "Fourth length check"); + + ls_pop_head(list); + mu_assert_eq((int)list->length, 1, "Fifth length check"); + + ls_insert(list, 2, (void *)&test2); + mu_assert_eq((int)list->length, 2, "Sixth length check"); + + ls_prepend(list, (void *)&test3); + mu_assert_eq((int)list->length, 3, "Seventh length check"); + + ls_del_n(list, 2); + mu_assert_eq((int)list->length, 2, "Eighth length check"); + + ls_append(list2, (void *)&test1); + ls_append(list2, (void *)&test3); + ls_append(list2, (void *)&test2); + ls_join(list, list2); + mu_assert_eq((int)list->length, 5, "Ninth length check"); + iter = list->head; + count = 0; + while (iter) { + count++; + iter = iter->n; + } + mu_assert_eq((int)list->length, count, "Tenth length check"); + ls_free(list); + ls_free(list2); + mu_end; +} + +bool test_r_list_sort5(void) { + SdbList *list = ls_new(); + int i = 0; + char *upper[] = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" }; + char *lower[] = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" }; + for (i = 0; i < 26; i++) { + ls_append(list, (void *)lower[i]); + } + for (i = 0; i < 26; i++) { + ls_append(list, (void *)upper[i]); + } + // add more than 43 elements to trigger merge sort + ls_sort(list, (SdbListComparator)strcmp); + mu_assert_streq((char *)list->head->data, upper[0], "First element"); + mu_assert_streq((char *)list->tail->data, lower[25], "Last element"); + ls_free(list); + mu_end; +} + +static int cmp_order(const void *a, const void *b) { + int ra = *(int *)a; + int rb = *(int *)b; + return ra > rb; +} + +bool test_r_list_sort6(void) { + int values[] = { + 4640, + 5152, + 5664, + 6176, + 6688, + 7200, + 7712, + 32, + 544, + 1056, + 1568, + 2080, + 2592, + 3104, + 3616, + 4128, + 4640, + 5152, + 5664, + 6176, + 6688, + 7200, + 7712, + 8224, + 8273, + 8337, + 8356, + 8452, + 8577, + 8625, + 8641, + 8657, + 8673, + 8689, + 8736, + 9248, + 9760, + 10272, + 10784, + 11296, + 11808, + 12320, + 12832, + 13344, + 13856, + 14368, + 14880, + 15392, + 15904, + 16416, + 16928, + 17440, + 17952, + 18464, + 18976, + 19488, + 20000, + 20512, + 21024, + 21536, + 22048, + 22560, + 23072, + 23584, + 24096, + 24608, + 8768, + 9792, + 10816, + 11840, + 12864, + 13888, + 14912, + 15936, + 16960, + 17984, + 19008, + 20032, + 21056, + 22080, + 23104, + 24128, + 25152, + 26176, + 27200, + 28224, + 29248, + 30272, + 31296, + 32320, + 33344, + 34368, + 35392, + 36416, + 37440, + 38464, + 39488, + 40512, + 8832, + 10880, + 12928, + 14976, + 17024, + 19072, + 21120, + 23168, + 25216, + 27264, + 29312, + 31360, + 33408, + 35456, + 37504, + 39552, + }; + int i; + int a, b; + SdbListIter *iter; + SdbList *list = ls_new(); + + for (i = 0; i < R_ARRAY_SIZE(values); i++) { + ls_append(list, (void *)&values[i]); + } + + ls_merge_sort(list, (SdbListComparator)cmp_order); + + a = *(int *)list->head->data; + for (iter = list->head->n, i = 0; iter; iter = iter->n, i++) { + b = *(int *)iter->data; +#if 0 // Debug print + printf("Element %d : %d < %d\n", i+1, a, b); +#endif + mu_assert("nth element not inferior or equal to next", a <= b); + a = b; + } + ls_free(list); + mu_end; +} + +bool test_r_list_sort4(void) { + SdbList *list = ls_new(); + char *test1 = "AAAA"; + char *test2 = "BBBB"; + char *test3 = "CCCC"; + char *test4 = "DDDD"; + char *test5 = "EEEE"; + char *test6_later = "FFFF"; + char *test7 = "GGGG"; + char *test8 = "HHHH"; + char *test9 = "IIII"; + char *test10 = "JJJJ"; + char *ins_tests_odd[] = { test10, test1, test3, test7, test5, test9, test2, + test4, test8 }; + char *exp_tests_odd[] = { test1, test2, test3, test4, test5, test7, + test8, test9, test10 }; + int i; + + // Put in not sorted order. + for (i = 0; i < R_ARRAY_SIZE(ins_tests_odd); ++i) { + ls_append(list, (void *)ins_tests_odd[i]); + } + // Sort. + ls_merge_sort(list, (SdbListComparator)strcmp); + + // Check that the list (odd-length) is actually sorted. + SdbListIter *next = list->head; + for (i = 0; i < R_ARRAY_SIZE(exp_tests_odd); ++i) { + char buf[BUF_LENGTH]; + snprintf(buf, BUF_LENGTH, "%d-th value in sorted list", i); + mu_assert_streq((char *)next->data, exp_tests_odd[i], buf); + next = next->n; + } + + char *data; + printf("after sorted 1 \n"); + ls_foreach (list, next, data) { + printf("l -> %s\n", data); + } + + char *exp_tests_even[] = { test1, test2, test3, test4, test5, + test6_later, test7, test8, test9, test10 }; + // Add test6 to make the length even + ls_append(list, (void *)test6_later); + printf("after adding FFFF \n"); + ls_foreach (list, next, data) { + printf("l -> %s\n", data); + } + // Sort + ls_merge_sort(list, (SdbListComparator)strcmp); + printf("after sorting 2 \n"); + ls_foreach (list, next, data) { + printf("l -> %s\n", data); + } + // Check that the list (even-length) is actually sorted. + next = list->head; + for (i = 0; i < R_ARRAY_SIZE(exp_tests_even); ++i) { + char buf[BUF_LENGTH]; + snprintf(buf, BUF_LENGTH, "%d-th value in sorted list", i); + mu_assert_streq((char *)next->data, exp_tests_even[i], buf); + next = next->n; + } + ls_free(list); + mu_end; +} + +int all_tests() { + mu_run_test(test_r_list_size); + mu_run_test(test_r_list_values); + mu_run_test(test_ls_join); + mu_run_test(test_ls_free); + mu_run_test(test_ls_del_n); + mu_run_test(test_r_list_sort); + mu_run_test(test_r_list_sort2); + mu_run_test(test_r_list_sort3); + mu_run_test(test_r_list_sort4); + mu_run_test(test_r_list_sort5); + mu_run_test(test_r_list_sort6); + mu_run_test(test_ls_length); + return tests_passed != tests_run; +} + +int main(int argc, char **argv) { + return all_tests(); +} diff --git a/test/unit/test_sdb_sdb.c b/test/unit/test_sdb_sdb.c new file mode 100644 index 00000000000..18bbc44b552 --- /dev/null +++ b/test/unit/test_sdb_sdb.c @@ -0,0 +1,579 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#include "minunit.h" +#include +#include +#include +#include + +static bool foreach_delete_cb(void *user, const char *key, const char *val) { + if (strcmp(key, "bar")) { + sdb_unset(user, key, 0); + } + return 1; +} + +bool test_sdb_foreach_delete(void) { + Sdb *db = sdb_new(NULL, NULL, false); + sdb_set(db, "foo", "bar", 0); + sdb_set(db, "bar", "cow", 0); + sdb_set(db, "low", "bar", 0); + sdb_foreach(db, foreach_delete_cb, db); + + mu_assert("Item not deleted", !(int)(size_t)sdb_const_get(db, "foo", NULL)); + mu_assert("Item not deleted", (int)(size_t)sdb_const_get(db, "bar", NULL)); + + sdb_free(db); + mu_end; +} + +bool test_sdb_list_delete(void) { + Sdb *db = sdb_new(NULL, NULL, false); + sdb_set(db, "foo", "bar", 0); + sdb_set(db, "bar", "cow", 0); + sdb_set(db, "low", "bar", 0); + SdbList *list = sdb_foreach_list(db, true); + SdbListIter *iter; + SdbKv *kv; + ls_foreach (list, iter, kv) { + // printf ("--> %s\n", kv->key); + sdb_unset(db, kv->base.key, 0); + } + SdbList *list2 = sdb_foreach_list(db, true); + mu_assert("List is empty", !ls_length(list2)); + ls_free(list); + ls_free(list2); + sdb_free(db); + mu_end; +} + +bool test_sdb_list_big(void) { + Sdb *db = sdb_new0(); + int i; + for (i = 0; i < 5000; i++) { + sdb_num_set(db, sdb_fmt("%d", i), i + 1, 0); + } + SdbList *list = sdb_foreach_list(db, true); + // TODO: verify if its sorted + ls_free(list); + sdb_free(db); + mu_end; +} + +bool test_sdb_delete_none(void) { + Sdb *db = sdb_new(NULL, NULL, false); + sdb_set(db, "foo", "bar", 0); + sdb_set(db, "bar", "cow", 0); + sdb_set(db, "low", "bar", 0); + sdb_unset(db, "fundas", 0); + sdb_unset(db, "barnas", 0); + sdb_unset(db, "bar", 0); + sdb_unset(db, "pinuts", 0); + SdbList *list = sdb_foreach_list(db, false); + mu_assert_eq(ls_length(list), 2, "Unmatched rows"); + ls_free(list); + sdb_free(db); + mu_end; +} + +bool test_sdb_delete_alot(void) { + Sdb *db = sdb_new(NULL, NULL, false); + const int count = 2048; + int i; + + for (i = 0; i < count; i++) { + sdb_set(db, sdb_fmt("key.%d", i), "bar", 0); + } + for (i = 0; i < count; i++) { + sdb_unset(db, sdb_fmt("key.%d", i), 0); + } + SdbList *list = sdb_foreach_list(db, false); + mu_assert_eq(ls_length(list), 0, "Unmatched rows"); + ls_free(list); + sdb_free(db); + + mu_end; +} + +bool test_sdb_milset(void) { + int i = 0; + const int MAX = 1999; + Sdb *s = sdb_new0(); + sdb_set(s, "foo", "bar", 0); + for (i = 0; i < MAX; i++) { + if (!sdb_set(s, "foo", "bar", 0)) { + mu_assert("milset: sdb_set failed", 0); + break; + } + } + sdb_free(s); + mu_end; +} + +bool test_sdb_milset_random(void) { + int i = 0; + const int MAX = 1999; + bool solved = true; + Sdb *s = sdb_new0(); + sdb_set(s, "foo", "bar", 0); + for (i = 0; i < MAX; i++) { + char *v = sdb_fmt("bar%d", i); + if (!sdb_set(s, "foo", v, 0)) { + solved = false; + break; + } + } + mu_assert("milset: sdb_set", solved); + sdb_free(s); + mu_end; +} + +bool test_sdb_namespace(void) { + bool solved = false; + const char *dbname = ".bar"; + Sdb *s = sdb_new0(); + if (!s) { + return false; + } + unlink(dbname); + Sdb *n = sdb_ns(s, dbname, 1); + if (n) { + sdb_set(n, "user.pancake", "pancake foo", 0); + sdb_set(n, "user.password", "jklsdf8r3o", 0); + /* FIXED BUG1 ns_sync doesnt creates the database file */ + sdb_ns_sync(s); + // sdb_sync (n); + /* FIXED BUG2 crash in free */ + sdb_free(s); + + int fd = open(dbname, O_RDONLY); + if (fd != -1) { + close(fd); + solved = true; + } + unlink(dbname); + } + mu_assert("namespace sync", solved); + rz_file_rm(".tmp"); + mu_end; +} + +static bool foreach_filter_user_cb(void *user, const char *key, const char *val) { + Sdb *db = (Sdb *)user; + const char *v = sdb_const_get(db, key, NULL); + if (!v) { + return false; + } + return key[0] == 'b' && v[0] == 'c'; +} + +bool test_sdb_foreach_filter_user(void) { + Sdb *db = sdb_new(NULL, NULL, false); + sdb_set(db, "crow", NULL, 0); + sdb_set(db, "foo", "bar", 0); + sdb_set(db, "bar", "cow", 0); + sdb_set(db, "bag", "horse", 0); + sdb_set(db, "boo", "cow", 0); + sdb_set(db, "bog", "horse", 0); + sdb_set(db, "low", "bar", 0); + sdb_set(db, "bip", "cow", 0); + sdb_set(db, "big", "horse", 0); + SdbList *ls = sdb_foreach_list_filter_user(db, foreach_filter_user_cb, true, db); + SdbListIter *it = ls_iterator(ls); + HtPPKv *kv = ls_iter_get(it); + mu_assert_streq((const char *)kv->key, "bar", "list should be sorted"); + mu_assert_streq((const char *)kv->value, "cow", "list should be filtered"); + kv = ls_iter_get(it); + mu_assert_streq((const char *)kv->key, "bip", "list should be sorted"); + mu_assert_streq((const char *)kv->value, "cow", "list should be filtered"); + kv = ls_iter_get(it); + mu_assert_streq((const char *)kv->key, "boo", "list should be sorted"); + mu_assert_streq((const char *)kv->value, "cow", "list should be filtered"); + mu_assert_null(it, "list should be terminated"); + ls_free(ls); + sdb_free(db); + mu_end; +} + +static bool foreach_filter_cb(void *user, const char *key, const char *val) { + return key[0] == 'b'; +} + +bool test_sdb_foreach_filter(void) { + Sdb *db = sdb_new(NULL, NULL, false); + sdb_set(db, "foo", "bar", 0); + sdb_set(db, "bar", "cow", 0); + sdb_set(db, "boo", "cow", 0); + sdb_set(db, "low", "bar", 0); + sdb_set(db, "bip", "cow", 0); + SdbList *ls = sdb_foreach_list_filter(db, foreach_filter_cb, true); + SdbListIter *it = ls_iterator(ls); + HtPPKv *kv = ls_iter_get(it); + mu_assert_streq((const char *)kv->key, "bar", "list should be sorted"); + kv = ls_iter_get(it); + mu_assert_streq((const char *)kv->key, "bip", "list should be sorted"); + kv = ls_iter_get(it); + mu_assert_streq((const char *)kv->key, "boo", "list should be sorted"); + mu_assert_null(it, "list should be terminated"); + ls_free(ls); + sdb_free(db); + mu_end; +} + +bool test_sdb_copy() { + Sdb *src = sdb_new0(); + sdb_set(src, "i am", "thou", 0); + sdb_set(src, "thou art", "i", 0); + Sdb *sub = sdb_ns(src, "subns", true); + sdb_set(sub, "rizin", "cool", 0); + sdb_set(sub, "cutter", "cooler", 0); + + Sdb *dst = sdb_new0(); + sdb_copy(src, dst); + sdb_free(src); + + mu_assert_eq(sdb_count(dst), 2, "root count"); + mu_assert_streq(sdb_const_get(dst, "i am", 0), "thou", "root entries"); + mu_assert_streq(sdb_const_get(dst, "thou art", 0), "i", "root entries"); + mu_assert_eq(ls_length(dst->ns), 1, "sub ns count"); + Sdb *dst_sub = sdb_ns(dst, "subns", false); + mu_assert_notnull(dst_sub, "subns"); + mu_assert_eq(sdb_count(dst_sub), 2, "sub ns entries count"); + mu_assert_streq(sdb_const_get(dst_sub, "rizin", 0), "cool", "sub ns entries"); + mu_assert_streq(sdb_const_get(dst_sub, "cutter", 0), "cooler", "sub ns entries"); + + sdb_free(dst); + mu_end; +} + +#define PERTURBATOR "\\,\";]\n [\r}{'=/" + +static const char *text_ref_simple = + "/\n" + "aaa=stuff\n" + "bbb=other stuff\n" + "somekey=somevalue\n" + "\n" + "/subnamespace\n" + "\\/more stuff=in sub\n" + "key in sub=value in sub\n" + "\n" + "/subnamespace/subsub\n" + "some stuff=also down here\n"; + +// the order in here is implementation-defined +static const char *text_ref_simple_unsorted = + "/\n" + "aaa=stuff\n" + "somekey=somevalue\n" + "bbb=other stuff\n" + "\n" + "/subnamespace\n" + "key in sub=value in sub\n" + "\\/more stuff=in sub\n" + "\n" + "/subnamespace/subsub\n" + "some stuff=also down here\n"; + +static const char *text_ref = + "/\n" + "\\\\,\";]\\n [\\r}{'\\=/key\\\\,\";]\\n [\\r}{'\\=/=\\\\,\";]\\n [\\r}{'=/value\\\\,\";]\\n [\\r}{'=/\n" + "aaa=stuff\n" + "bbb=other stuff\n" + "\n" + "/sub\\\\,\";]\\n [\\r}{'=\\/namespace\n" + "\\/more stuff\\n=\\nin\\nsub\\n\n" + "key\\\\,\";]\\n [\\r}{'\\=/in sub=value\\\\,\";]\\n [\\r}{'=/in sub\n" + "\n" + "/sub\\\\,\";]\\n [\\r}{'=\\/namespace/subsub\n" + "some stuff=also down here\n"; + +static const char *text_ref_bad_nl = + "/\r\n" + "non=unix\r" + "newlines=should\n" + "be=banned"; + +static const char *text_ref_broken = + "/////\n" + "just garbage\n" + "no\\=equal\\=here\n" + "=nokey\n" + "novalue=\n" + "more/garbage/////\n" + "\\/\\/\\/unnecessary=\\/escapes\\u\\x\\a\n" + "////some/////subns/\n" + "more=equal=signs=than=one=\n" + "////some//subns//more\n" + "////some//subns//more/further//////\n" + "escape=newlines\\\n" + "also escape=nothingness\\"; + +static const char *text_ref_path_last_line = + "/\r\n" + "some=stuff\r" + "/a/useless/namespace"; + +static Sdb *text_ref_simple_db() { + Sdb *db = sdb_new0(); + sdb_set(db, "somekey", "somevalue", 0); + sdb_set(db, "aaa", "stuff", 0); + sdb_set(db, "bbb", "other stuff", 0); + + Sdb *sub = sdb_ns(db, "subnamespace", true); + sdb_set(sub, "key in sub", "value in sub", 0); + sdb_set(sub, "/more stuff", "in sub", 0); + + Sdb *subsub = sdb_ns(sub, "subsub", true); + sdb_set(subsub, "some stuff", "also down here", 0); + return db; +} + +static Sdb *text_ref_db() { + Sdb *db = sdb_new0(); + sdb_set(db, PERTURBATOR "key" PERTURBATOR, PERTURBATOR "value" PERTURBATOR, 0); + sdb_set(db, "aaa", "stuff", 0); + sdb_set(db, "bbb", "other stuff", 0); + + Sdb *sub = sdb_ns(db, "sub" PERTURBATOR "namespace", true); + sdb_set(sub, "key" PERTURBATOR "in sub", "value" PERTURBATOR "in sub", 0); + sdb_set(sub, "/more stuff\n", "\nin\nsub\n", 0); + + Sdb *subsub = sdb_ns(sub, "subsub", true); + sdb_set(subsub, "some stuff", "also down here", 0); + return db; +} + +static Sdb *text_ref_bad_nl_db() { + Sdb *db = sdb_new0(); + sdb_set(db, "non", "unix", 0); + sdb_set(db, "newlines", "should", 0); + sdb_set(db, "be", "banned", 0); + return db; +} + +static Sdb *text_ref_broken_db() { + Sdb *db = sdb_new0(); + sdb_set(db, "///unnecessary", "/escapesuxa", 0); + Sdb *a = sdb_ns(db, "some", true); + Sdb *b = sdb_ns(a, "subns", true); + sdb_set(b, "more", "equal=signs=than=one=", 0); + Sdb *c = sdb_ns(b, "more", true); + Sdb *d = sdb_ns(c, "further", true); + sdb_set(d, "escape", "newlines", 0); + sdb_set(d, "also escape", "nothingness", 0); + return db; +} + +static Sdb *text_ref_path_last_line_db() { + Sdb *db = sdb_new0(); + sdb_set(db, "some", "stuff", 0); + Sdb *a = sdb_ns(db, "a", true); + Sdb *b = sdb_ns(a, "useless", true); + sdb_ns(b, "namespace", true); + return db; +} + +static int tmpfile_new(const char *filename, const char *buf, size_t sz) { + int fd = open(filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0644); + if (fd < 0) { + return -1; + } + if (!buf) { + return fd; + } + int w = write(fd, buf, sz); + if (w < (int)sz) { + close(fd); + return -1; + } + lseek(fd, 0, SEEK_SET); + return fd; +} + +#define TEST_BUF_SZ 0x1000 + +bool test_sdb_text_save_simple() { + Sdb *db = text_ref_simple_db(); + + int fd = tmpfile_new(".text_save_simple", NULL, 0); + bool succ = sdb_text_save_fd(db, fd, true); + lseek(fd, 0, SEEK_SET); + char buf[TEST_BUF_SZ] = { 0 }; + mu_assert_neq(-1, read(fd, buf, sizeof(buf) - 1), "read succeed"); + close(fd); + unlink(".text_save_simple"); + + sdb_free(db); + + mu_assert_true(succ, "save success"); + mu_assert_streq(buf, text_ref_simple, "text save"); + + mu_end; +} + +bool test_sdb_text_save_simple_unsorted() { + Sdb *db = text_ref_simple_db(); + + int fd = tmpfile_new(".text_save_simple_unsorted", NULL, 0); + bool succ = sdb_text_save_fd(db, fd, false); + lseek(fd, 0, SEEK_SET); + char buf[TEST_BUF_SZ] = { 0 }; + mu_assert_neq(-1, read(fd, buf, sizeof(buf) - 1), "read succeed"); + close(fd); + unlink(".text_save_simple_unsorted"); + + sdb_free(db); + + mu_assert_true(succ, "save success"); + mu_assert_streq(buf, text_ref_simple_unsorted, "text save"); + + mu_end; +} + +bool test_sdb_text_save() { + Sdb *db = text_ref_db(); + + int fd = tmpfile_new(".text_save", NULL, 0); + bool succ = sdb_text_save_fd(db, fd, true); + lseek(fd, 0, SEEK_SET); + char buf[TEST_BUF_SZ] = { 0 }; + mu_assert_neq(-1, read(fd, buf, sizeof(buf) - 1), "read succeed"); + close(fd); + unlink(".text_save"); + + sdb_free(db); + + mu_assert_true(succ, "save success"); + mu_assert_streq(buf, text_ref, "text save"); + + mu_end; +} + +static void diff_cb(const SdbDiff *diff, void *user) { + char buf[2048]; + if (sdb_diff_format(buf, sizeof(buf), diff) < 0) { + return; + } + printf("%s\n", buf); +} + +bool test_sdb_text_load_simple() { + char *buf = strdup(text_ref_simple); + Sdb *db = sdb_new0(); + bool succ = sdb_text_load_buf(db, buf, strlen(buf)); + free(buf); + + mu_assert_true(succ, "load success"); + Sdb *ref_db = text_ref_simple_db(); + bool eq = sdb_diff(ref_db, db, diff_cb, NULL); + sdb_free(ref_db); + sdb_free(db); + mu_assert_true(eq, "load correct"); + mu_end; +} + +bool test_sdb_text_load() { + char *buf = strdup(text_ref); + Sdb *db = sdb_new0(); + bool succ = sdb_text_load_buf(db, buf, strlen(buf)); + free(buf); + + mu_assert_true(succ, "load success"); + Sdb *ref_db = text_ref_db(); + bool eq = sdb_diff(ref_db, db, diff_cb, NULL); + sdb_free(ref_db); + sdb_free(db); + mu_assert_true(eq, "load correct"); + mu_end; +} + +bool test_sdb_text_load_bad_nl() { + char *buf = strdup(text_ref_bad_nl); + Sdb *db = sdb_new0(); + bool succ = sdb_text_load_buf(db, buf, strlen(buf)); + free(buf); + + mu_assert_true(succ, "load success"); + Sdb *ref_db = text_ref_bad_nl_db(); + bool eq = sdb_diff(ref_db, db, diff_cb, NULL); + sdb_free(ref_db); + sdb_free(db); + mu_assert_true(eq, "load correct"); + mu_end; +} + +bool test_sdb_text_load_broken() { + char *buf = strdup(text_ref_broken); + Sdb *db = sdb_new0(); + bool succ = sdb_text_load_buf(db, buf, strlen(buf)); + free(buf); + + mu_assert_true(succ, "load success"); + Sdb *ref_db = text_ref_broken_db(); + bool eq = sdb_diff(ref_db, db, diff_cb, NULL); + sdb_free(ref_db); + sdb_free(db); + mu_assert_true(eq, "load correct"); + mu_end; +} + +bool test_sdb_text_load_path_last_line() { + char *buf = strdup(text_ref_path_last_line); + Sdb *db = sdb_new0(); + bool succ = sdb_text_load_buf(db, buf, strlen(buf)); + free(buf); + + mu_assert_true(succ, "load success"); + Sdb *ref_db = text_ref_path_last_line_db(); + bool eq = sdb_diff(ref_db, db, diff_cb, NULL); + sdb_free(ref_db); + sdb_free(db); + mu_assert_true(eq, "load correct"); + mu_end; +} + +bool test_sdb_text_load_file() { + close(tmpfile_new(".text_load_simple", text_ref_simple, strlen(text_ref_simple))); + Sdb *db = sdb_new0(); + bool succ = sdb_text_load(db, ".text_load_simple"); + unlink(".text_load_simple"); + + mu_assert_true(succ, "load success"); + Sdb *ref_db = text_ref_simple_db(); + bool eq = sdb_diff(ref_db, db, diff_cb, NULL); + sdb_free(ref_db); + sdb_free(db); + mu_assert_true(eq, "load correct"); + mu_end; +} + +int all_tests() { + // XXX two bugs found with crash + mu_run_test(test_sdb_namespace); + mu_run_test(test_sdb_foreach_delete); + mu_run_test(test_sdb_list_delete); + mu_run_test(test_sdb_delete_none); + mu_run_test(test_sdb_delete_alot); + mu_run_test(test_sdb_milset); + mu_run_test(test_sdb_milset_random); + mu_run_test(test_sdb_list_big); + mu_run_test(test_sdb_foreach_filter_user); + mu_run_test(test_sdb_foreach_filter); + mu_run_test(test_sdb_copy); + mu_run_test(test_sdb_text_save_simple); + mu_run_test(test_sdb_text_save_simple_unsorted); + mu_run_test(test_sdb_text_load_simple); + mu_run_test(test_sdb_text_save); + mu_run_test(test_sdb_text_load); + mu_run_test(test_sdb_text_load_bad_nl); + mu_run_test(test_sdb_text_load_broken); + mu_run_test(test_sdb_text_load_path_last_line); + mu_run_test(test_sdb_text_load_file); + return tests_passed != tests_run; +} + +int main(int argc, char **argv) { + return all_tests(); +} diff --git a/test/unit/test_sdb_util.c b/test/unit/test_sdb_util.c new file mode 100644 index 00000000000..251c75c1282 --- /dev/null +++ b/test/unit/test_sdb_util.c @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: pancake +// SPDX-License-Identifier: MIT + +#include "minunit.h" +#include +#include +#include + +bool test_sdb_itoa_null_arg() { + mu_assert_streq_free(sdb_itoa(0, NULL, 10), "0", "0 is converted"); + mu_assert_streq_free(sdb_itoa(10, NULL, 10), "10", "10 is converted"); + mu_assert_streq_free(sdb_itoa(3, NULL, 10), "3", "3 is converted"); + mu_assert_streq_free(sdb_itoa(100, NULL, 16), "0x64", "100 is converted"); + mu_assert_streq_free(sdb_itoa(100, NULL, 10), "100", "100 is converted"); + mu_end; +} + +bool test_sdb_itoa() { + char s[64]; + mu_assert_streq(sdb_itoa(0, s, 10), "0", "0 is converted"); + mu_assert_streq(sdb_itoa(10, s, 10), "10", "10 is converted"); + mu_assert_streq(sdb_itoa(3, s, 10), "3", "3 is converted"); + mu_assert_streq(sdb_itoa(100, s, 16), "0x64", "100 is converted"); + mu_assert_streq(sdb_itoa(100, s, 10), "100", "100 is converted"); + mu_end; +} + +int all_tests() { + mu_run_test(test_sdb_itoa_null_arg); + mu_run_test(test_sdb_itoa); + return tests_passed != tests_run; +} + +int main(int argc, char **argv) { + return all_tests(); +}