SDB_API int sdb_array_add_sorted_num(Sdb *s, const char *key, ut64 val, ut32 cas) { int i; char valstr[64]; 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 != NULL; i++) { if (val <= sdb_atoi(n)) break; sdb_const_anext(n, &n); } if (n == NULL) i = -1; sdb_array_insert_num (s, key, i, val, cas); return 0; }
SDB_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; }
// TODO: done, but there's room for improvement SDB_API int sdb_array_insert(Sdb *s, const char *key, int idx, const char *val, ut32 cas) { int lnstr, lstr, lval; const char *str = sdb_const_get_len (s, key, &lstr, 0); char *x, *ptr; if (!str || !*str) return sdb_set (s, key, val, cas); lval = strlen (val); lstr--; //lstr = strlen (str); // we can optimize this by caching value len in memory . add sdb_const_get_size() x = malloc (lval + lstr + 2); if (idx==-1) { memcpy (x, str, lstr); x[lstr] = SDB_RS; memcpy (x+lstr+1, val, lval+1); } else if (idx == 0) { memcpy (x, val, lval); x[lval] = SDB_RS; memcpy (x+lval+1, str, lstr+1); } else { char *nstr = malloc (lstr+1); memcpy (nstr, str, lstr+1); ptr = (char *)Aindexof (nstr, idx); if (ptr) { int lptr = (nstr+lstr+1)-ptr; *(ptr-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); }
// TODO: Add APIs to resize meta? nope, just del and add R_API int r_meta_set_string(RAnal *a, int type, ut64 addr, const char *s) { char key[100], val[2048], *e_str; int ret; ut64 size; snprintf (key, sizeof (key)-1, "meta.%c", type); sdb_array_add_num (DB, key, addr, 0); snprintf (key, sizeof (key)-1, "meta.%c.0x%"PFMT64x, type, addr); size = sdb_array_get_num (DB, key, 0, 0); if (!size) { size = strlen (s); meta_inrange_add (a, addr, size); ret = R_TRUE; } else ret = R_FALSE; e_str = sdb_encode ((const void*)s, -1); snprintf (val, sizeof (val)-1, "%d,%s", (int)size, e_str); sdb_set (DB, key, val, 0); free ((void*)e_str); return ret; }
SDB_API int sdb_array_set(Sdb *s, const char *key, int idx, const char *val, ut32 cas) { char *ptr; int lstr, lval, len; const char *usr, *str = sdb_const_get_len (s, key, &lstr, 0); 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); 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; }
R_API int r_meta_set_var_comment (RAnal *a, int type, ut64 idx, ut64 addr, const char *s) { char key[100], val[2048], *e_str; int ret; ut64 size; int space_idx = a->meta_spaces.space_idx; meta_type_add (a, type, addr); snprintf (key, sizeof (key)-1, "meta.%c.0x%"PFMT64x".0x%"PFMT64x, type, addr, idx); size = sdb_array_get_num (DB, key, 0, 0); if (!size) { size = strlen (s); meta_inrange_add (a, addr, size); ret = true; } else ret = false; e_str = sdb_encode ((const void*)s, -1); snprintf (val, sizeof (val)-1, "%d,%d,%s", (int)size, space_idx, e_str); sdb_set (DB, key, val, 0); free ((void*)e_str); return ret; }
static int trace_hook_mem_read(RAnalEsil *esil, ut64 addr, ut8 *buf, int len) { char *hexbuf = malloc ((1+len)*3); int ret = 0; if (esil->cb.mem_read) { ret = esil->cb.mem_read (esil, addr, buf, len); } sdb_array_add_num (DB, KEY ("mem.read"), addr, 0); r_hex_bin2str (buf, len, hexbuf); sdb_set (DB, KEYAT ("mem.read.data", addr), hexbuf, 0); eprintf ("[ESIL] MEM READ 0x%08"PFMT64x" %s\n", addr, hexbuf); free (hexbuf); if (ocbs.hook_mem_read) { RAnalEsilCallbacks cbs = esil->cb; esil->cb = ocbs; ret = ocbs.hook_mem_read (esil, addr, buf, len); esil->cb = cbs; } return ret; }
static int meta_add(RAnal *a, int type, int subtype, ut64 from, ut64 to, const char *str) { int space_idx = a->meta_spaces.space_idx; char *e_str, key[100], val[2048]; int exists; if (from > to) { return false; } if (from == to) { to = from + 1; } if (type == 100 && (to - from) < 1) { return false; } /* set entry */ e_str = sdb_encode ((const void*)str, -1); RAnalMetaItem mi = {from, to, (int)(to - from), type, subtype, e_str, space_idx}; meta_serialize (&mi, key, sizeof (key), val, sizeof (val)); exists = sdb_exists (DB, key); sdb_set (DB, key, val, 0); free (e_str); // XXX: This is totally inefficient, using array_add withuot // checking return value is wrong practice, also it may lead // to inconsistent DB, and pretty bad performance. We should // store this list in a different storage that doesnt have // those limits and it's O(1) instead of O(n) snprintf (key, sizeof (key) - 1, "meta.0x%"PFMT64x, from); if (exists) { const char *value = sdb_const_get (DB, key, 0); int idx = sdb_array_indexof (DB, key, value, 0); sdb_array_delete (DB, key, idx, 0); } snprintf (val, sizeof (val)-1, "%c", type); sdb_array_add (DB, key, val, 0); meta_inrange_add (a, from, to - from); return true; }
R_API int r_meta_add(RAnal *a, int type, ut64 from, ut64 to, const char *str) { int exists; char *e_str, key[100], val[2048]; if (from>to) return R_FALSE; if (from == to) to = from+1; if (type == 100) { if ((to-from)<3) { return R_FALSE; } } /* set entry */ e_str = sdb_encode ((const void*)str, -1); snprintf (key, sizeof (key)-1, "meta.%c.0x%"PFMT64x, type, from); snprintf (val, sizeof (val)-1, "%d,%s", (int)(to-from), e_str); exists = sdb_exists (DB, key); sdb_set (DB, key, val, 0); free (e_str); // XXX: This is totally inefficient, using array_add withuot // checking return value is wrong practice, also it may lead // to inconsistent DB, and pretty bad performance. We should // store this list in a different storage that doesnt have // those limits and it's O(1) instead of O(n) if (!exists) { /* set type index */ snprintf (key, sizeof (key)-1, "meta.0x%"PFMT64x, from); snprintf (val, sizeof (val)-1, "%c", type); sdb_array_add (DB, key, val, 0); /* set type index */ snprintf (key, sizeof (key)-1, "meta.%c", type); sdb_array_add_num (DB, key, from, 0); } return R_TRUE; }
static bool addItem(RAnal *a, RSignItem *it) { char key[R_SIGN_KEY_MAXSZ], val[R_SIGN_VAL_MAXSZ]; const char *curval; bool retval = true; RSignItem *curit = R_NEW0 (RSignItem); serialize (a, it, key, val); curval = sdb_const_get (a->sdb_zigns, key, 0); if (curval) { if (!deserialize (a, curit, key, curval)) { eprintf ("error: cannot deserialize zign\n"); retval = false; goto exit_function; } mergeItem (curit, it); serialize (a, curit, key, val); } sdb_set (a->sdb_zigns, key, val, 0); exit_function: free (curit); return retval; }
R_API void r_cons_pal_update_event() { Sdb *db = sdb_new0 (); int i, n = 0; char **color; /* Compute cons->pal values */ for (i = 0; keys[i].name; i++) { RColor *rcolor = RCOLOR_AT (i); color = COLOR_AT (i); // Color is dynamically allocated, needs to be freed if (*color) { R_FREE (*color); } *color = r_cons_rgb_str (NULL, 0, rcolor); const char *rgb = sdb_fmt ("rgb:%02x%02x%02x", rcolor->r, rcolor->g, rcolor->b); sdb_set (db, rgb, "1", 0); } SdbList *list = sdb_foreach_list (db, true); SdbListIter *iter; SdbKv *kv; r_cons_rainbow_free (); r_cons_rainbow_new (list->length); ls_foreach (list, iter, kv) { r_cons_singleton ()->pal.rainbow[n++] = strdup (kv->key); }
static void cons_pal_update_event(RConsContext *ctx) { Sdb *db = sdb_new0 (); int i, n = 0; char **color; /* Compute cons->pal values */ for (i = 0; keys[i].name; i++) { RColor *rcolor = (RColor *) (((ut8 *) &(ctx->cpal)) + keys[i].coff); color = (char **) (((ut8 *) &(ctx->pal)) + keys[i].off); // Color is dynamically allocated, needs to be freed if (*color) { R_FREE (*color); } *color = r_cons_rgb_str_mode (ctx->color, NULL, 0, rcolor); const char *rgb = sdb_fmt ("rgb:%02x%02x%02x", rcolor->r, rcolor->g, rcolor->b); sdb_set (db, rgb, "1", 0); } SdbList *list = sdb_foreach_list (db, true); SdbListIter *iter; SdbKv *kv; r_cons_rainbow_free (ctx); r_cons_rainbow_new (ctx, list->length); ls_foreach (list, iter, kv) { ctx->pal.rainbow[n++] = strdup (sdbkv_key (kv)); }
SDB_VISIBLE int sdb_setn(Sdb *s, const char *key, ut64 v, ut32 cas) { char b[128]; sdb_itoa (v, b); return sdb_set (s, key, b, cas); }
static int r_bin_mz_init_hdr(struct r_bin_mz_obj_t* bin) { int relocations_size, dos_file_size; if (!(bin->dos_header = R_NEW0 (MZ_image_dos_header))) { r_sys_perror ("malloc (MZ_image_dos_header)"); return false; } // TODO: read field by field to avoid endian and alignment issues if (r_buf_read_at (bin->b, 0, (ut8*)bin->dos_header, sizeof (*bin->dos_header)) == -1) { eprintf ("Error: read (MZ_image_dos_header)\n"); return false; } if (bin->dos_header->blocks_in_file < 1) { return false; } dos_file_size = ((bin->dos_header->blocks_in_file - 1) << 9) + \ bin->dos_header->bytes_in_last_block; bin->dos_file_size = dos_file_size; if (dos_file_size > bin->size) { return false; } relocations_size = bin->dos_header->num_relocs * sizeof (MZ_image_relocation_entry); if ((bin->dos_header->reloc_table_offset + relocations_size) > bin->size) { return false; } sdb_num_set (bin->kv, "mz.initial.cs", bin->dos_header->cs, 0); sdb_num_set (bin->kv, "mz.initial.ip", bin->dos_header->ip, 0); sdb_num_set (bin->kv, "mz.initial.ss", bin->dos_header->ss, 0); sdb_num_set (bin->kv, "mz.initial.sp", bin->dos_header->sp, 0); sdb_num_set (bin->kv, "mz.overlay_number", bin->dos_header->overlay_number, 0); sdb_num_set (bin->kv, "mz.dos_header.offset", 0, 0); sdb_set (bin->kv, "mz.dos_header.format", "[2]zwwwwwwwwwwwww" " signature bytes_in_last_block blocks_in_file num_relocs " " header_paragraphs min_extra_paragraphs max_extra_paragraphs " " ss sp checksum ip cs reloc_table_offset overlay_number ", 0); bin->dos_extended_header_size = bin->dos_header->reloc_table_offset - \ sizeof (MZ_image_dos_header); if (bin->dos_extended_header_size > 0) { if (!(bin->dos_extended_header = malloc (bin->dos_extended_header_size))) { r_sys_perror ("malloc (dos extended header)"); return false; } if (r_buf_read_at (bin->b, sizeof (MZ_image_dos_header), (ut8*)bin->dos_extended_header, bin->dos_extended_header_size) == -1) { eprintf ("Error: read (dos extended header)\n"); return false; } } if (relocations_size > 0) { if (!(bin->relocation_entries = malloc (relocations_size))) { r_sys_perror ("malloc (dos relocation entries)"); return false; } if (r_buf_read_at (bin->b, bin->dos_header->reloc_table_offset, (ut8*)bin->relocation_entries, relocations_size) == -1) { eprintf ("Error: read (dos relocation entries)\n"); R_FREE (bin->relocation_entries); return false; } } return true; }
R_API int r_core_pseudo_code (RCore *core, const char *input) { Sdb *db; RAnalFunction *fcn = r_anal_get_fcn_in (core->anal, core->offset, R_ANAL_FCN_TYPE_NULL); int asmpseudo = r_config_get_i (core->config, "asm.pseudo"); int asmdecode = r_config_get_i (core->config, "asm.decode"); int asmlines = r_config_get_i (core->config, "asm.lines"); int asmbytes = r_config_get_i (core->config, "asm.bytes"); int asmoffset = r_config_get_i (core->config, "asm.offset"); int asmflags = r_config_get_i (core->config, "asm.flags"); int asmfcnlines = r_config_get_i (core->config, "asm.fcnlines"); int asmcomments = r_config_get_i (core->config, "asm.comments"); int asmfunctions = r_config_get_i (core->config, "asm.functions"); if (!fcn) { eprintf ("Cannot find function in 0x%08"PFMT64x"\n", core->offset); return R_FALSE; } r_config_set_i (core->config, "asm.pseudo", 1); r_config_set_i (core->config, "asm.decode", 0); r_config_set_i (core->config, "asm.lines", 0); r_config_set_i (core->config, "asm.bytes", 0); r_config_set_i (core->config, "asm.offset", 0); r_config_set_i (core->config, "asm.flags", 0); r_config_set_i (core->config, "asm.fcnlines", 0); r_config_set_i (core->config, "asm.comments", 0); r_config_set_i (core->config, "asm.functions", 0); db = sdb_new0 (); /* */ // walk all basic blocks // define depth level for each block // use it for indentation // asm.pseudo=true // asm.decode=true RAnalBlock *bb = r_list_first (fcn->bbs); char indentstr[1024]; int n_bb = r_list_length (fcn->bbs); r_cons_printf ("function %s () {", fcn->name); int indent = 1; int nindent = 1; do { #define I_TAB 4 #define K_ELSE(x) sdb_fmt(0,"else.%"PFMT64x,x) #define K_INDENT(x) sdb_fmt(0,"loc.%"PFMT64x,x) #define SET_INDENT(x) { memset (indentstr, ' ', x*I_TAB); indentstr [(x*I_TAB)-2] = 0; } if (!bb) break; r_cons_push (); char *code = r_core_cmd_str (core, sdb_fmt(0, "pDI %d @ 0x%08"PFMT64x"\n", bb->size, bb->addr)); r_cons_pop (); memset (indentstr, ' ', indent*I_TAB); indentstr [(indent*I_TAB)-2] = 0; code = r_str_prefix_all (code, indentstr); code[strlen(code)-1] = 0; // chop last newline //r_cons_printf ("\n%s loc_0x%llx:\n", indentstr, bb->addr); //if (nindent != indent) { // r_cons_printf ("\n%s loc_0x%llx:\n", indentstr, bb->addr); //} r_cons_printf ("\n%s loc_0x%llx:\n", indentstr, bb->addr); indentstr[(indent*I_TAB)-2] = 0; r_cons_printf ("\n%s", code); free (code); if (sdb_get (db, K_INDENT(bb->addr), 0)) { // already analyzed, go pop and continue // XXX check if cant pop //eprintf ("%s// 0x%08llx already analyzed\n", indentstr, bb->addr); ut64 addr = sdb_array_pop_num (db, "indent", NULL); if (addr==UT64_MAX) { int i; nindent = 1; for (i=indent; i!=nindent; i--) { SET_INDENT (i); r_cons_printf ("\n%s}", indentstr); } r_cons_printf ("\n%sreturn;\n", indentstr); break; } if (sdb_num_get (db, K_ELSE(bb->addr), 0)) { r_cons_printf ("\n%s} else {", indentstr); } else { r_cons_printf ("\n%s}", indentstr); } r_cons_printf ("\n%s goto loc_0x%llx", indentstr, addr); bb = r_anal_bb_from_offset (core->anal, addr); if (!bb) { eprintf ("failed block\n"); break; } //eprintf ("next is %llx\n", addr); nindent = sdb_num_get (db, K_INDENT(addr), NULL); if (indent>nindent) { int i; for (i=indent; i!=nindent; i--) { SET_INDENT (i); r_cons_printf ("\n%s}", indentstr); } } indent = nindent; } else { sdb_set (db, K_INDENT(bb->addr), "passed", 0); if (bb->jump != UT64_MAX) { int swap = 1; // TODO: determine which branch take first ut64 jump = swap? bb->jump: bb->fail; ut64 fail = swap? bb->fail: bb->jump; // if its from another function chop it! RAnalFunction *curfcn = r_anal_get_fcn_in (core->anal, jump, R_ANAL_FCN_TYPE_NULL); if (curfcn != fcn) { // chop that branch r_cons_printf ("\n // chop\n"); break; } if (sdb_get (db, K_INDENT(jump), 0)) { // already tracekd if (!sdb_get (db, K_INDENT(fail), 0)) { bb = r_anal_bb_from_offset (core->anal, fail); } } else { bb = r_anal_bb_from_offset (core->anal, jump); if (!bb) { eprintf ("failed to retrieve blcok at 0x%"PFMT64x"\n", jump); break; } if (fail != UT64_MAX) { // do not push if already pushed indent++; if (sdb_get (db, K_INDENT(bb->fail), 0)) { /* do nothing here */ eprintf ("BlockAlready 0x%"PFMT64x"\n", bb->addr); } else { // r_cons_printf (" { RADICAL %llx\n", bb->addr); sdb_array_push_num (db, "indent", fail, 0); sdb_num_set (db, K_INDENT(fail), indent, 0); sdb_num_set (db, K_ELSE(fail), 1, 0); r_cons_printf (" {"); } } else { r_cons_printf (" do"); sdb_array_push_num (db, "indent", jump, 0); sdb_num_set (db, K_INDENT(jump), indent, 0); sdb_num_set (db, K_ELSE(jump), 1, 0); r_cons_printf (" {"); indent++; } } } else { ut64 addr = sdb_array_pop_num (db, "indent", NULL); if (addr==UT64_MAX) { r_cons_printf ("\nbreak\n"); break; } bb = r_anal_bb_from_offset (core->anal, addr); nindent = sdb_num_get (db, K_INDENT(addr), NULL); if (indent>nindent) { int i; for (i=indent; i!=nindent; i--) { SET_INDENT (i); r_cons_printf ("\n%s}", indentstr); } } if (nindent != indent) { r_cons_printf ("\n%s} else {\n", indentstr); } indent = nindent; } } //n_bb --; } while (n_bb>0); r_cons_printf ("}\n"); r_cons_flush (); r_config_set_i (core->config, "asm.pseudo", asmpseudo); r_config_set_i (core->config, "asm.decode", asmdecode); r_config_set_i (core->config, "asm.lines", asmlines); r_config_set_i (core->config, "asm.bytes", asmbytes); r_config_set_i (core->config, "asm.offset", asmoffset); r_config_set_i (core->config, "asm.flags", asmflags); r_config_set_i (core->config, "asm.fcnlines", asmfcnlines); r_config_set_i (core->config, "asm.comments", asmcomments); r_config_set_i (core->config, "asm.functions", asmfunctions); sdb_free (db); return R_TRUE; }
SDB_VISIBLE char *sdb_querys (Sdb *s, char *buf, size_t len, const char *cmd) { const char *q; char *p, *eq, *ask; int i, ok, w, alength; ut64 n; if (!s) return NULL; if (cmd == NULL) { cmd = buf; buf = NULL; } if (!len || !buf) buf = malloc ((len=32)); ask = strchr (cmd, '?'); if (*cmd == '+' || *cmd == '-') { *buf = 0; if (ask) { *ask = 0; if (*cmd=='+') n = sdb_json_inc (s, cmd+1, ask+1, 1, 0); else n = sdb_json_dec (s, cmd+1, ask+1, 1, 0); *ask = '?'; } else { if (*cmd=='+') n = sdb_inc (s, cmd+1, 1, 0); else n = sdb_dec (s, cmd+1, 1, 0); } w = snprintf (buf, len-1, "%"ULLFMT"d", n); if (w<0 || (size_t)w>len) { buf = malloc (0xff); snprintf (buf, 0xff, "%"ULLFMT"d", n); } return buf; } else if (*cmd == '(') { p = strchr (cmd, ')'); if (!p) { fprintf (stderr, "Missing ')'.\n"); return NULL; } *p = 0; eq = strchr (p+1, '='); if (cmd[1]=='?') { // if (!eq) { ... alength = sdb_alength (s, p+1); w = snprintf (buf, len, "%d", alength); if (w<0 || (size_t)w>len) { buf = malloc (32); snprintf (buf, 32, "%d", alength); } return buf; } if (cmd[1]) { /* (+)foo=bla (-)foo=bla */ if ((cmd[1]=='+'||cmd[1]=='-') && !cmd[2]) { if (eq) { *eq = 0; if (cmd[1]=='+') { if (sdb_agetv (s, p+1, eq+1, 0)== -1) sdb_aset (s, p+1, -1, eq+1, 0); } else { sdb_adels (s, p+1, eq+1, 0); } return NULL; } else { if (cmd[1]=='+') { // (+)foo :: remove first element sdb_adel (s, p+1, 0, 0); } else { // (-)foo :: remove last element sdb_adel (s, p+1, -1, 0); } return NULL; } } else { i = atoi (cmd+1); if (eq) { *eq = 0; ok = eq[1]? ( (cmd[1]=='+')? sdb_ains (s, p+1, i, eq+1, 0): sdb_aset (s, p+1, i, eq+1, 0) ): sdb_adel (s, p+1, i, 0); if (ok) *buf = 0; else buf = NULL; return buf; } return sdb_aget (s, p+1, i, NULL); } } else { if (eq) { char *q, *out = strdup (eq+1); *eq = 0; // TODO: define new printable separator character for (q=out; *q; q++) if (*q==',') *q = SDB_RS; ok = sdb_set (s, p+1, out, 0); free (out); if (ok) { *buf = 0; return buf; } } else { const char *out = sdb_getc (s, p+1, 0); size_t wl; if (!out) return NULL; wl = strlen (out); if (wl>len) buf = malloc (wl+2); for (i=0; out[i]; i++) { if (out[i+1]) buf[i] = out[i]==SDB_RS? '\n': out[i]; else buf[i] = out[i]; } buf[i] = 0; return buf; } } } else { eq = strchr (cmd, '='); if (eq) { // 1 0 kvpath=value // 1 1 kvpath?jspath=value if (ask>eq) ask = NULL; *eq++ = 0; if (ask) { *ask++ = 0; ok = sdb_json_set (s, cmd, ask, eq, 0); } else ok = sdb_set (s, cmd, eq, 0); if (!ok) return NULL; *buf = 0; return buf; } else { // 0 1 kvpath?jspath // 0 0 kvpath if (ask) { *ask++ = 0; // TODO: not optimized to reuse 'buf' if ((p = sdb_json_get (s, cmd, ask, 0))) return p; } else { // sdbget if (!(q = sdb_getc (s, cmd, 0))) return NULL; if (strlen (q)> len) return strdup (q); strcpy (buf, q); return buf; } } } return NULL; }
// set if not defined SDB_VISIBLE 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); }
static int lmf_header_load(lmf_header *lmfh, RBuffer *buf, Sdb *db) { if (r_buf_size (buf) < sizeof (lmf_header)) { return false; } if (r_buf_fread_at (buf, QNX_HEADER_ADDR, (ut8 *) lmfh, "iiiiiiiicccciiiicc", 1) < QNX_HDR_SIZE) { return false; } sdb_set (db, "qnx.version", sdb_fmt ("0x%xH", lmfh->version), 0); sdb_set (db, "qnx.cflags", sdb_fmt ("0x%xH", lmfh->cflags), 0); sdb_set (db, "qnx.cpu", sdb_fmt ("0x%xH", lmfh->cpu), 0); sdb_set (db, "qnx.fpu", sdb_fmt ("0x%xH", lmfh->fpu), 0); sdb_set (db, "qnx.code_index", sdb_fmt ("0x%x", lmfh->code_index), 0); sdb_set (db, "qnx.stack_index", sdb_fmt ("0x%x", lmfh->stack_index), 0); sdb_set (db, "qnx.heap_index", sdb_fmt ("0x%x", lmfh->heap_index), 0); sdb_set (db, "qnx.argv_index", sdb_fmt ("0x%x", lmfh->argv_index), 0); sdb_set (db, "qnx.code_offset", sdb_fmt ("0x%x", lmfh->code_offset), 0); sdb_set (db, "qnx.stack_nbytes", sdb_fmt ("0x%x", lmfh->stack_nbytes), 0); sdb_set (db, "qnx.heap_nbytes", sdb_fmt ("0x%x", lmfh->heap_nbytes), 0); sdb_set (db, "qnx.image_base", sdb_fmt ("0x%x", lmfh->image_base), 0); return true; }
static int r_bin_mz_init_hdr(struct r_bin_mz_obj_t* bin) { int relocations_size, dos_file_size; if (!(bin->dos_header = malloc (sizeof(MZ_image_dos_header)))) { r_sys_perror ("malloc (MZ_image_dos_header)"); return R_FALSE; } if (r_buf_read_at (bin->b, 0, (ut8*)bin->dos_header, sizeof(*bin->dos_header)) == -1) { eprintf ("Error: read (MZ_image_dos_header)\n"); return R_FALSE; } if (bin->dos_header->blocks_in_file < 1) return R_FALSE; dos_file_size = ((bin->dos_header->blocks_in_file - 1) << 9) + \ bin->dos_header->bytes_in_last_block; bin->dos_file_size = dos_file_size; if (dos_file_size > bin->size) return R_FALSE; relocations_size = bin->dos_header->num_relocs * \ sizeof(MZ_image_relocation_entry); /* Check if relocation table doesn't exceed dos binary size */ if ((bin->dos_header->reloc_table_offset + relocations_size) > \ dos_file_size) return R_FALSE; sdb_num_set (bin->kv, "mz.initial.cs", bin->dos_header->cs, 0); sdb_num_set (bin->kv, "mz.initial.ip", bin->dos_header->ip, 0); sdb_num_set (bin->kv, "mz.initial.ss", bin->dos_header->ss, 0); sdb_num_set (bin->kv, "mz.initial.sp", bin->dos_header->sp, 0); sdb_num_set (bin->kv, "mz.overlay_number", bin->dos_header->overlay_number, 0); sdb_num_set (bin->kv, "mz.dos_header.offset", 0, 0); sdb_set (bin->kv, "mz.dos_header.format", "[2]zwwwwwwwwwwwww" " signature bytes_in_last_block blocks_in_file num_relocs " " header_paragraphs min_extra_paragraphs max_extra_paragraphs " " ss sp checksum ip cs reloc_table_offset overlay_number ", 0); bin->dos_extended_header_size = bin->dos_header->reloc_table_offset - \ sizeof(MZ_image_dos_header); if (bin->dos_extended_header_size > 0) { if (!(bin->dos_extended_header = \ malloc (bin->dos_extended_header_size))) { r_sys_perror ("malloc (dos extended header)"); return R_FALSE; } if (r_buf_read_at (bin->b, sizeof(MZ_image_dos_header), (ut8*)bin->dos_extended_header, bin->dos_extended_header_size) == -1) { eprintf ("Error: read (dos extended header)\n"); return R_FALSE; } } if (relocations_size > 0) { if (!(bin->relocation_entries = malloc (relocations_size))) { r_sys_perror ("malloc (dos relocation entries)"); return R_FALSE; } if (r_buf_read_at (bin->b, bin->dos_header->reloc_table_offset, (ut8*)bin->relocation_entries, relocations_size) == -1) { eprintf ("Error: read (dos relocation entries)\n"); return R_FALSE; } } return R_TRUE; }
// TODO: done, but there's room for improvement SDB_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); }
SDB_API int sdb_json_set (Sdb *s, const char *k, const char *p, const char *v, ut32 cas) { const char *beg[3]; const char *end[3]; int jslen, l, idx, len[3]; char *b, *str = NULL; const char *js; Rangstr rs; ut32 c; if (!s || !k) return 0; js = sdb_const_get_len (s, k, &jslen, &c); if (!js) { b = malloc (strlen (p)+strlen (v)+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 1; } return 0; } if (cas && c != cas) { return 0; } rs = json_get (js, p); if (!rs.p) { char *b = malloc (jslen+strlen(k)+strlen (v)+32); if (b) { int curlen, is_str = isstring (v); const char *q = is_str?"\"":""; const char *e = ""; // XX: or comma if (js[0] && js[1] != '}') e = ","; curlen = sprintf (b, "{\"%s\":%s%s%s%s", p, q, v, q, e); strcpy (b+curlen, js+1); // transfer ownership sdb_set_owned (s, k, b, cas); return 1; } // invalid json? return 0; } #define WLEN(x) (int)(size_t)(end[x]-beg[x]) 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); int msz = len[0]+len[1]+len[2]+strlen (v); if (msz<1) return 0; str = malloc (msz); if (!str) return 0; idx = len[0]; memcpy (str, beg[0], idx); if (is_str) { if (beg[2][0]!='"') { str[idx]='"'; idx++; } } else { if (beg[2][0]=='"') { idx--; } } 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]++; } } 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] = R_MAX (1, kidx-1); if (kidx==1) { if (beg[2][0]=='"') beg[2]++; beg[2]++; } str = malloc (len[0]+len[2]+1); if (!str) return 0; memcpy (str, beg[0], len[0]); if (!*beg[2]) beg[2]--; memcpy (str+len[0], beg[2], len[2]); str[len[0]+len[2]] = 0; } sdb_set_owned (s, k, str, cas); return 1; }
// XXX: cmd is reused SDB_API char *sdb_querys (Sdb *s, char *buf, size_t len, const char *cmd) { int i, d, ok, w, alength, bufset = 0; const char *p, *q, *val = NULL; char *eq, *ask; ut64 n; if (!s) return NULL; if (cmd == NULL) { cmd = buf; buf = NULL; } if (!len || !buf) { bufset = 1; buf = malloc ((len=64)); } ask = strchr (cmd, '?'); if (*cmd == '[') { char *tp = strchr (cmd, ']'); if (!tp) { fprintf (stderr, "Missing ']'.\n"); if (bufset) free (buf); return NULL; } *tp++ = 0; p = (const char *)tp; } else p = cmd; eq = strchr (p, '='); if (eq) { *eq++ = 0; if (*eq=='$') val = sdb_getc (s, eq+1, 0); } if (!val) val = eq; if (*cmd=='$') cmd = sdb_getc (s, cmd+1, 0); // cmd = val // cmd is key and val is value if (*cmd == '.') { sdb_query_file (s, cmd+1); if (bufset) free (buf); return NULL; } else if (*cmd == '+' || *cmd == '-') { d = 1; *buf = 0; if (val) { d = sdb_atoi (val); if (d) { if (*cmd=='+') sdb_inc (s, cmd+1, d, 0); else sdb_dec (s, cmd+1, d, 0); } else { sdb_concat (s, cmd+1, val, 0); } } else { if (ask) { *ask = 0; if (*cmd=='+') n = sdb_json_inc (s, cmd+1, ask+1, d, 0); else n = sdb_json_dec (s, cmd+1, ask+1, d, 0); *ask = '?'; } else { if (*cmd=='+') n = sdb_inc (s, cmd+1, d, 0); else n = sdb_dec (s, cmd+1, d, 0); } w = snprintf (buf, len-1, "%"ULLFMT"d", n); if (w<0 || (size_t)w>len) { buf = malloc (0xff); snprintf (buf, 0xff, "%"ULLFMT"d", n); } } return buf; } else if (*cmd == '[') { // [?] - count elements of array if (cmd[1]=='?') { // if (!eq) { ... alength = sdb_alength (s, p); w = snprintf (buf, len, "%d", alength); if (w<0 || (size_t)w>len) { buf = malloc (32); snprintf (buf, 31, "%d", alength); } return buf; } if (cmd[1]=='+'||cmd[1]=='-') { // [+]foo remove first element */ // [+]foo=bar PUSH */ // [-]foo POP */ // [-]foo=xx POP (=xx ignored) */ if (!cmd[2] || cmd[2] ==']') { // insert if (eq) { if (cmd[1]=='+') { if (sdb_agetv (s, p, val, 0)== -1) sdb_aset (s, p, -1, val, 0); } else { sdb_adels (s, p, val, 0); } return NULL; } else { char *ret; if (cmd[1]=='+') { // XXX: this is a little strange syntax to remove an item ret = sdb_aget (s, p, 0, 0); // (+)foo :: remove first element sdb_adel (s, p, 0, 0); } else { // POP ret = sdb_aget (s, p, -1, 0); // (-)foo :: remove last element sdb_adel (s, p, -1, 0); } return ret; } } else { // get/set specific element in array /* (+3)foo=bla */ i = atoi (cmd+1); if (eq) { ok = cmd[1]? ( (cmd[1]=='+')? sdb_ains (s, p, i, val, 0): sdb_aset (s, p, i, val, 0) ): sdb_adel (s, p, i, 0); if (ok) *buf = 0; else buf = NULL; return buf; } return sdb_aget (s, p, i, NULL); } } else { if (eq) { /* (3)foo=bla */ char *q, *out = strdup (val); // TODO: define new printable separator character for (q=out; *q; q++) if (*q==',') *q = SDB_RS; if (cmd[1]) { int idx = atoi (cmd+1); ok = sdb_aset (s, p, idx, val, 0); // TODO: handle when idx > sdb_alen } else { ok = sdb_set (s, p, out, 0); } free (out); if (ok) { *buf = 0; return buf; } return NULL; } else { /* (3)foo */ const char *out = sdb_getc (s, p, 0); size_t wl; if (cmd[1]) { i = atoi (cmd+1); return sdb_aget (s, p, i, NULL); } if (!out) return NULL; wl = strlen (out); if (wl>len) buf = malloc (wl+2); for (i=0; out[i]; i++) { if (out[i+1]) buf[i] = out[i]==SDB_RS? '\n': out[i]; else buf[i] = out[i]; } buf[i] = 0; return buf; } } } else { if (eq) { // 1 0 kvpath=value // 1 1 kvpath?jspath=value if (ask>eq) ask = NULL; if (ask) { *ask++ = 0; ok = sdb_json_set (s, cmd, ask, val, 0); } else ok = sdb_set (s, cmd, val, 0); if (!ok) { if (bufset) free (buf); return NULL; } *buf = 0; return buf; } else { // 0 1 kvpath?jspath // 0 0 kvpath if (ask) { *ask++ = 0; // TODO: not optimized to reuse 'buf' if ((p = sdb_json_get (s, cmd, ask, 0))) return strdup (p); } else { // sdbget if (!(q = sdb_getc (s, cmd, 0))) return NULL; if (strlen (q)> len) return strdup (q); strcpy (buf, q); return buf; } } } if (bufset) free (buf); return NULL; }
R_API int r_core_pseudo_code(RCore *core, const char *input) { Sdb *db; ut64 queuegoto = 0LL; const char *blocktype = "else"; RAnalFunction *fcn = r_anal_get_fcn_in (core->anal, core->offset, R_ANAL_FCN_TYPE_NULL); RConfigHold *hc = r_config_hold_new (core->config); if (!hc) { return false; } r_config_save_num (hc, "asm.pseudo", "asm.decode", "asm.lines", "asm.bytes", NULL); r_config_save_num (hc, "asm.offset", "asm.flags", "asm.fcnlines", "asm.comments", NULL); r_config_save_num (hc, "asm.functions", "asm.section", "asm.cmtcol", "asm.filter", NULL); r_config_save_num (hc, "scr.color", "asm.emustr", "asm.emu", "asm.emuwrite", NULL); if (!fcn) { eprintf ("Cannot find function in 0x%08"PFMT64x"\n", core->offset); r_config_hold_free (hc); return false; } r_config_set_i (core->config, "scr.color", 0); r_config_set_i (core->config, "asm.pseudo", 1); r_config_set_i (core->config, "asm.decode", 0); r_config_set_i (core->config, "asm.filter", 1); r_config_set_i (core->config, "asm.lines", 0); r_config_set_i (core->config, "asm.bytes", 0); r_config_set_i (core->config, "asm.offset", 0); r_config_set_i (core->config, "asm.flags", 0); r_config_set_i (core->config, "asm.emu", 1); r_config_set_i (core->config, "asm.emustr", 1); r_config_set_i (core->config, "asm.emuwrite", 1); r_config_set_i (core->config, "asm.fcnlines", 0); r_config_set_i (core->config, "asm.comments", 1); r_config_set_i (core->config, "asm.functions", 0); r_config_set_i (core->config, "asm.tabs", 0); r_config_set_i (core->config, "asm.section", 0); r_config_set_i (core->config, "asm.cmtcol", 30); r_core_cmd0 (core, "aeim"); db = sdb_new0 (); /* */ // walk all basic blocks // define depth level for each block // use it for indentation // asm.pseudo=true // asm.decode=true RAnalBlock *bb = r_list_first (fcn->bbs); char indentstr[1024]; int n_bb = r_list_length (fcn->bbs); r_cons_printf ("function %s () {", fcn->name); int indent = 1; int nindent = 1; do { #define I_TAB 4 #define K_MARK(x) sdb_fmt(0,"mark.%"PFMT64x,x) #define K_ELSE(x) sdb_fmt(0,"else.%"PFMT64x,x) #define K_INDENT(x) sdb_fmt(0,"loc.%"PFMT64x,x) #define SET_INDENT(x) { memset (indentstr, ' ', x*I_TAB); indentstr [(x*I_TAB)-2] = 0; } if (!bb) break; r_cons_push (); char *code = r_core_cmd_str (core, sdb_fmt (0, "pD %d @ 0x%08"PFMT64x"\n", bb->size, bb->addr)); r_cons_pop (); memset (indentstr, ' ', indent * I_TAB); indentstr [(indent * I_TAB) - 2] = 0; code = r_str_prefix_all (code, indentstr); int len = strlen (code); code[len - 1] = 0; // chop last newline //r_cons_printf ("\n%s loc_0x%llx:\n", indentstr, bb->addr); //if (nindent != indent) { // r_cons_printf ("\n%s loc_0x%llx:\n", indentstr, bb->addr); //} find_and_change (code, len); if (!sdb_const_get (db, K_MARK (bb->addr), 0)) { bool mustprint = !queuegoto || queuegoto != bb->addr; if (mustprint) { if (queuegoto) { r_cons_printf ("\n%s goto loc_0x%llx", indentstr, queuegoto); queuegoto = 0LL; } r_cons_printf ("\n%s loc_0x%llx:\n", indentstr, bb->addr); indentstr[(indent * I_TAB) - 2] = 0; r_cons_printf ("\n%s", code); free (code); sdb_num_set (db, K_MARK (bb->addr), 1, 0); } } if (sdb_const_get (db, K_INDENT (bb->addr), 0)) { // already analyzed, go pop and continue // XXX check if cant pop //eprintf ("%s// 0x%08llx already analyzed\n", indentstr, bb->addr); ut64 addr = sdb_array_pop_num (db, "indent", NULL); if (addr == UT64_MAX) { int i; nindent = 1; for (i = indent; i != nindent; i--) { SET_INDENT (i); r_cons_printf ("\n%s}", indentstr); } r_cons_printf ("\n%sreturn;\n", indentstr); break; } if (sdb_num_get (db, K_ELSE (bb->addr), 0)) { if (!strcmp (blocktype, "else")) { r_cons_printf ("\n%s } %s {", indentstr, blocktype); } else { r_cons_printf ("\n%s } %s (?);", indentstr, blocktype); } } else { r_cons_printf ("\n%s}", indentstr); } if (addr != bb->addr) { queuegoto = addr; //r_cons_printf ("\n%s goto loc_0x%llx", indentstr, addr); } bb = r_anal_bb_from_offset (core->anal, addr); if (!bb) { eprintf ("failed block\n"); break; } //eprintf ("next is %llx\n", addr); nindent = sdb_num_get (db, K_INDENT (addr), NULL); if (indent > nindent && !strcmp (blocktype, "else")) { int i; for (i = indent; i != nindent; i--) { SET_INDENT (i); r_cons_printf ("\n%s }", indentstr); } } indent = nindent; } else { sdb_set (db, K_INDENT (bb->addr), "passed", 0); if (bb->jump != UT64_MAX) { int swap = 1; // TODO: determine which branch take first ut64 jump = swap ? bb->jump : bb->fail; ut64 fail = swap ? bb->fail : bb->jump; // if its from another function chop it! RAnalFunction *curfcn = r_anal_get_fcn_in (core->anal, jump, R_ANAL_FCN_TYPE_NULL); if (curfcn != fcn) { // chop that branch r_cons_printf ("\n // chop\n"); break; } if (sdb_get (db, K_INDENT (jump), 0)) { // already tracekd if (!sdb_get (db, K_INDENT (fail), 0)) { bb = r_anal_bb_from_offset (core->anal, fail); } } else { bb = r_anal_bb_from_offset (core->anal, jump); if (!bb) { eprintf ("failed to retrieve blcok at 0x%"PFMT64x"\n", jump); break; } if (fail != UT64_MAX) { // do not push if already pushed indent++; if (sdb_get (db, K_INDENT (bb->fail), 0)) { /* do nothing here */ eprintf ("BlockAlready 0x%"PFMT64x"\n", bb->addr); } else { // r_cons_printf (" { RADICAL %llx\n", bb->addr); sdb_array_push_num (db, "indent", fail, 0); sdb_num_set (db, K_INDENT (fail), indent, 0); sdb_num_set (db, K_ELSE (fail), 1, 0); SET_INDENT (indent); r_cons_printf ("\n%s {", indentstr); } } else { r_cons_printf ("\n%s do", indentstr); sdb_array_push_num (db, "indent", jump, 0); sdb_num_set (db, K_INDENT (jump), indent, 0); sdb_num_set (db, K_ELSE (jump), 1, 0); if (jump <= bb->addr) { blocktype = "while"; } else { blocktype = "else"; } r_cons_printf ("\n%s {", indentstr); indent++; } } } else { ut64 addr = sdb_array_pop_num (db, "indent", NULL); if (addr == UT64_MAX) { //r_cons_printf ("\nbreak\n"); break; } bb = r_anal_bb_from_offset (core->anal, addr); nindent = sdb_num_get (db, K_INDENT (addr), NULL); if (indent > nindent) { int i; for (i = indent; i != nindent; i--) { SET_INDENT (i); r_cons_printf ("\n%s}", indentstr); } } if (nindent != indent) { r_cons_printf ("\n%s} else {\n", indentstr); } indent = nindent; } } //n_bb --; } while (n_bb > 0); r_cons_printf ("\n}\n"); r_config_restore (hc); r_config_hold_free (hc); sdb_free (db); return true; }
R_API void r_anal_pin (RAnal *a, ut64 addr, const char *name) { char buf[64]; const char *key = sdb_itoa (addr, buf, 16); sdb_set (DB, key, name, 0); }
static int art_header_load(ARTHeader *art, RBuffer *buf, Sdb *db) { /* TODO: handle read errors here */ if (r_buf_size (buf) < sizeof (ARTHeader)) { return false; } (void) r_buf_fread_at (buf, 0, (ut8 *) art, "IIiiiiiiiiiiii", 1); sdb_set (db, "img.base", sdb_fmt (0, "0x%x", art->image_base), 0); sdb_set (db, "img.size", sdb_fmt (0, "0x%x", art->image_size), 0); sdb_set (db, "art.checksum", sdb_fmt (0, "0x%x", art->checksum), 0); sdb_set (db, "art.version", sdb_fmt (0, "%c%c%c", art->version[0], art->version[1], art->version[2]), 0); sdb_set (db, "oat.begin", sdb_fmt (0, "0x%x", art->oat_file_begin), 0); sdb_set (db, "oat.end", sdb_fmt (0, "0x%x", art->oat_file_end), 0); sdb_set (db, "oat_data.begin", sdb_fmt (0, "0x%x", art->oat_data_begin), 0); sdb_set (db, "oat_data.end", sdb_fmt (0, "0x%x", art->oat_data_end), 0); sdb_set (db, "patch_delta", sdb_fmt (0, "0x%x", art->patch_delta), 0); sdb_set (db, "image_roots", sdb_fmt (0, "0x%x", art->image_roots), 0); sdb_set (db, "compile_pic", sdb_fmt (0, "0x%x", art->compile_pic), 0); return true; }
SDB_VISIBLE int sdb_remove (Sdb* s, const char *key, ut32 cas) { return key? sdb_set (s, key, "", cas): 0; }