Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package radare2 for openSUSE:Factory checked 
in at 2026-08-09 21:44:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/radare2 (Old)
 and      /work/SRC/openSUSE:Factory/.radare2.new.16738 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "radare2"

Sun Aug  9 21:44:24 2026 rev:16 rq:1370348 version:6.2.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/radare2/radare2.changes  2026-06-17 
16:19:29.898023238 +0200
+++ /work/SRC/openSUSE:Factory/.radare2.new.16738/radare2.changes       
2026-08-09 21:46:33.073213475 +0200
@@ -1,0 +2,58 @@
+Fri Aug  7 20:44:11 UTC 2026 - Eyad Issa <[email protected]>
+
+- Update to version 6.2.0:
+  * ABI: reduce struct padding and unify type member models
+  * Analysis: improve conventions, types, propagation, and ESIL
+  * Analysis: add stack argument APIs, reguse hints, PIC seeding
+  * API: add formatted SDB getters and setters
+  * Arch: add stateful sessions and improve PPC, MIPS, ARM, and x86
+  * Bin: add generic resource extraction and demangler plugins
+  * Bin: harden PE, ELF, DEX, Java, Mach-O, NE, and QNX parsing
+  * Build/CI: add MiKNix, Android, JSLinux, and ReactOS support
+  * Console/Core: isolate tasks; improve grep, themes, and pdc
+  * Crash: fix OOB, UAF, overflow, injection, and OOM bugs
+  * Debug: improve cross-platform debugging, ptrace, regs, and dcut
+  * Disasm/ESIL: improve backward walks, call comments, and flags
+  * Graph/HTTP/IO: fix graph targets, auth, WebUI, and io.gprobe
+  * Magic/Port: improve matching and add browser NetBSD support
+  * Projects/Pseudo: improve persistence and structured pdc
+  * Search/Shell: overhaul contexts, parsing, substitution, APIs
+  * Sync/Tests: add Windows timeouts and eliminate test races
+  * Tools: improve Android toolchains, r2 flags, rax2, and rasm2
+  * Types: expand signatures, pointers, structs, unions, and sizes
+  * Util: improve base64, math, locks, paths, tries, and strbufs
+  * Visual/Windows: fix scrolling, prompts, debugger, ANSI support
+  * Security: harden GDB, HTTP auth, shell, and loaders
+
+-------------------------------------------------------------------
+Fri Jun 26 13:36:50 UTC 2026 - Eyad Issa <[email protected]>
+
+- Update to version 6.1.8:
+  * ABI: deprecate sdb_remove in favor of sdb_unset
+  * Analysis: improve trampolines, variables, DWARF, SDB, and tests
+  * Arch: harden PPC64 TOC tracking and improve endian handling
+  * Arch: update Capstone, CPU validation, and disassembly support
+  * ASM: apply the pseudo filter once and pad rasm2 output
+  * Bin: harden loaders; improve dyldcache, strings, relocations
+  * Bin: add FTAB extraction and C++, Rust, D, XL, ARM demanglers
+  * Build: add Nix support; improve SDB, Zydis, Meson, Capstone
+  * Console: fix task races, UAFs, marks, grep, and reset
+  * Core: fix numvar reference math state handling
+  * Crash: fix parser overflows, OOB reads, UAFs, null derefs
+  * Debug: fix profile leaks and GDB cache ownership checks
+  * Disasm: improve address bases, overlaps, comments, and pseudo
+  * ESIL: improve PPC, local state, stepping, traps, and step-back
+  * FS/IO: fix HFS+, NTFS, io.self leaks, and Windows block devices
+  * Print/Pseudo: improve JWT, JSON, markdown, pdc, and bounds
+  * Search/Shell: improve ESIL, gadgets, aliases, and parsing
+  * Socket: fix PTY dynapi initialization during redirection
+  * Tests/Tools: improve requirements, parsing, and rafs2 packaging
+  * Types/Util: improve lookups, unions, strings, and markdown
+  * Windows: port r2preload support
+- CVE-2026-8696: radare2: version 6.1.5 contains a use-after-free
+  vulnerability in the gdbr_pids_list() function within the GDB
+  client core that allows remote attackers to cause a denial of
+  service or potentially execute arbitrary code
+- Drop CVE-2026-8695.patch: upstreamed in 6.1.6
+
+-------------------------------------------------------------------

Old:
----
  CVE-2026-8695.patch
  quickjs-3087a2ce5bcb66cc1fcd9f34d3e5ce3bd43a67d9.tar.gz
  radare2-6.1.4.tar.gz
  sdb-2.4.2.tar.gz

New:
----
  quickjs-9d15fb60b67c45fd0de413bb49e48f8dacebac16.tar.gz
  radare2-6.2.0.tar.gz
  sdb-2.5.0.tar.gz
  zydis-4.1.0.tar.gz

----------(Old B)----------
  Old:  service or potentially execute arbitrary code
- Drop CVE-2026-8695.patch: upstreamed in 6.1.6
----------(Old E)----------

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ radare2.spec ++++++
--- /var/tmp/diff_new_pack.XX2NIt/_old  2026-08-09 21:46:34.253253754 +0200
+++ /var/tmp/diff_new_pack.XX2NIt/_new  2026-08-09 21:46:34.253253754 +0200
@@ -16,15 +16,17 @@
 #
 
 
-%global sdb_rev    2.4.2
-%global sdb_soname 2_4_2
+%global sdb_rev    2.5.0
+%global sdb_soname 2_5_0
 
-%global qjs_rev 3087a2ce5bcb66cc1fcd9f34d3e5ce3bd43a67d9
+%global zydis_rev  4.1.0
+
+%global qjs_rev 9d15fb60b67c45fd0de413bb49e48f8dacebac16
 
 %global tests_rev d6a4529fd6c8439a309abbcf00bb7a9efc9e8271
 
 Name:           radare2
-Version:        6.1.4
+Version:        6.2.0
 Release:        0
 Summary:        Reverse Engineering Framework
 License:        GPL-3.0-only AND LGPL-3.0-only
@@ -34,8 +36,8 @@
 Source1:        
https://github.com/radareorg/sdb/archive/%{sdb_rev}/sdb-%{sdb_rev}.tar.gz
 Source2:        
https://github.com/quickjs-ng/quickjs/archive/%{qjs_rev}/quickjs-%{qjs_rev}.tar.gz
 Source3:        
https://github.com/radareorg/radare2-testbins/archive/%{tests_rev}/radare2-testbins-%{tests_rev}.tar.gz
+Source4:        
https://github.com/zyantific/zydis/releases/download/v%{zydis_rev}/zydis-amalgamated.tar.gz#/zydis-%{zydis_rev}.tar.gz
 Patch0:         pkgconfig.patch
-Patch1:         
https://github.com/radareorg/radare2/commit/c213ad6894a1eb9086ac8bf5fae35757e9e1683c.patch#/CVE-2026-8695.patch
 BuildRequires:  chrpath
 BuildRequires:  dos2unix
 BuildRequires:  fdupes
@@ -107,6 +109,9 @@
 mkdir -p subprojects/qjs
 tar   -C subprojects/qjs --strip-components=1 -x -f %{SOURCE2}
 
+mkdir -p subprojects/zydis
+tar   -C subprojects/zydis -x -f %{SOURCE4}
+
 mkdir -p test/bins
 tar   -C test/bins --strip-components=1 -x -f %{SOURCE3}
 

++++++ quickjs-3087a2ce5bcb66cc1fcd9f34d3e5ce3bd43a67d9.tar.gz -> 
quickjs-9d15fb60b67c45fd0de413bb49e48f8dacebac16.tar.gz ++++++
++++ 21008 lines of diff (skipped)

++++++ radare2-6.1.4.tar.gz -> radare2-6.2.0.tar.gz ++++++
/work/SRC/openSUSE:Factory/radare2/radare2-6.1.4.tar.gz 
/work/SRC/openSUSE:Factory/.radare2.new.16738/radare2-6.2.0.tar.gz differ: char 
13, line 1

++++++ sdb-2.4.2.tar.gz -> sdb-2.5.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/.github/workflows/ci.yml 
new/sdb-2.5.0/.github/workflows/ci.yml
--- old/sdb-2.4.2/.github/workflows/ci.yml      2026-03-11 20:13:35.000000000 
+0100
+++ new/sdb-2.5.0/.github/workflows/ci.yml      2026-08-07 16:51:53.000000000 
+0200
@@ -10,7 +10,7 @@
   build-windows:
     runs-on: windows-latest
     steps:
-    - uses: actions/checkout@v6
+    - uses: actions/checkout@v7
     - name: Windows Dependencies
       run: pip install meson ninja
     - name: Building Sdb
@@ -23,7 +23,7 @@
   build-cxx:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v6
+    - uses: actions/checkout@v7
     - name: Building Sdb
       run: make xxx
     - name: Testing gperf
@@ -31,7 +31,7 @@
   build-python:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v6
+    - uses: actions/checkout@v7
     - name: Building Sdb
       run: export CFLAGS=-O0 ; make
     - name: Building python bindings
@@ -42,7 +42,7 @@
     runs-on: ubuntu-latest
     continue-on-error: true
     steps:
-    - uses: actions/checkout@v6
+    - uses: actions/checkout@v7
     - name: install gperf
       run: sudo apt install gperf
     - name: building sdb
@@ -55,7 +55,7 @@
 #    runs-on: ubuntu-latest
 #    continue-on-error: true
 #    steps:
-#    - uses: actions/checkout@v6
+#    - uses: actions/checkout@v7
 #    - name: install gperf
 #      run: sudo apt install gperf
 #    - name: building sdb
@@ -67,7 +67,7 @@
   build-linux:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v6
+    - uses: actions/checkout@v7
     - name: Meson Sdb
       run: |
         pip install meson ninja
@@ -90,7 +90,7 @@
   build-heap:
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v6
+    - uses: actions/checkout@v7
     - name: Building Sdb
       run: make heapasan
     - name: Running tests
@@ -100,7 +100,7 @@
   build-macos:
     runs-on: macos-latest
     steps:
-    - uses: actions/checkout@v6
+    - uses: actions/checkout@v7
     - name: Building Sdb
       run: make -j4 && cd ..
     - name: Packaging
@@ -133,7 +133,7 @@
         sys: [MINGW64, UCRT64]
     runs-on: windows-latest
     steps:
-    - uses: actions/checkout@v6
+    - uses: actions/checkout@v7
     - name: Setup MSYS2
       uses: msys2/setup-msys2@v2
       with:
@@ -174,7 +174,7 @@
     runs-on: ubuntu-24.04
     steps:
       - name: Checkout code
-        uses: actions/checkout@v6
+        uses: actions/checkout@v7
         with:
           fetch-depth: 0 # Download all git history and tags
       - name: Check if is a release
@@ -201,7 +201,7 @@
         dist/artifacts/sdb_cydia/*.deb
     steps:
       - name: Checkout code
-        uses: actions/checkout@v6
+        uses: actions/checkout@v7
         with:
           fetch-depth: 0 # Download all git history and tags
       - name: Extract sdb version
@@ -214,7 +214,7 @@
           path: dist/artifacts
       - name: Create GitHub release
         id: create_release
-        uses: 
softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
+        uses: 
softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
         with:
           name: ${{ steps.version.outputs.branch }}
           tag_name: ${{ needs.check_release.outputs.tag_name }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/.github/workflows/codeql.yml 
new/sdb-2.5.0/.github/workflows/codeql.yml
--- old/sdb-2.4.2/.github/workflows/codeql.yml  2026-03-11 20:13:35.000000000 
+0100
+++ new/sdb-2.5.0/.github/workflows/codeql.yml  2026-08-07 16:51:53.000000000 
+0200
@@ -24,7 +24,7 @@
 
     steps:
       - name: Checkout
-        uses: actions/checkout@v6
+        uses: actions/checkout@v7
 
       - name: Initialize CodeQL
         uses: github/codeql-action/init@v4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/config.mk new/sdb-2.5.0/config.mk
--- old/sdb-2.4.2/config.mk     2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/config.mk     2026-08-07 16:51:53.000000000 +0200
@@ -1,4 +1,4 @@
-SDBVER=2.4.2
+SDBVER=2.5.0
 
 PREFIX?=/usr
 BINDIR=${PREFIX}/bin
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/include/sdb/cdb_make.h 
new/sdb-2.5.0/include/sdb/cdb_make.h
--- old/sdb-2.4.2/include/sdb/cdb_make.h        2026-03-11 20:13:35.000000000 
+0100
+++ new/sdb-2.5.0/include/sdb/cdb_make.h        2026-08-07 16:51:53.000000000 
+0200
@@ -35,6 +35,7 @@
 int cdb_make_addbegin(struct cdb_make *, unsigned int, unsigned int);
 int cdb_make_addend(struct cdb_make *, unsigned int, unsigned int,ut32);
 int cdb_make_add(struct cdb_make *, const char *, unsigned int, const char *, 
unsigned int);
+void cdb_make_cancel(struct cdb_make *);
 int cdb_make_finish(struct cdb_make *);
 
 #endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/include/sdb/sdb.h 
new/sdb-2.5.0/include/sdb/sdb.h
--- old/sdb-2.4.2/include/sdb/sdb.h     2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/include/sdb/sdb.h     2026-08-07 16:51:53.000000000 +0200
@@ -178,9 +178,10 @@
 SDB_API char *sdb_querysf(Sdb* s, char *buf, size_t buflen, const char *fmt, 
...);
 SDB_API int sdb_query_file(Sdb *s, const char* file);
 SDB_API bool sdb_exists(Sdb*, const char *key);
-SDB_API bool sdb_remove(Sdb*, const char *key, ut32 cas);
 SDB_API int sdb_unset(Sdb*, const char *key, ut32 cas);
 SDB_API int sdb_nunset(Sdb*, ut64 nkey, ut32 cas);
+SDB_API bool sdb_rename(Sdb*, const char *oldkey, const char *newkey, ut32 
cas);
+SDB_API int sdb_rename_prefix(Sdb*, const char *oldprefix, const char 
*newprefix);
 SDB_API int sdb_unset_like(Sdb *s, const char *k);
 SDB_API char** sdb_like(Sdb *s, const char *k, const char *v, 
SdbForeachCallback cb);
 
@@ -228,11 +229,13 @@
 
 // Gets a const pointer to the value associated with `key`
 SDB_API const char *sdb_const_get(Sdb*, const char *key, ut32 *cas);
+SDB_API const char *sdb_const_getf(Sdb*, ut32 *cas, const char *fmt, ...) 
SDB_PRINTF_CHECK(3, 4);
 
 // Gets a const pointer to the value associated with `key` and returns in
 // `vlen` the length of the value string.
 SDB_API const char *sdb_const_get_len(Sdb* s, const char *key, int *vlen, ut32 
*cas);
 SDB_API int sdb_set(Sdb*, const char *key, const char *data, ut32 cas);
+SDB_API int sdb_setf(Sdb*, const char *data, ut32 cas, const char *fmt, ...) 
SDB_PRINTF_CHECK(4, 5);
 SDB_API int sdb_nset(Sdb*, ut64 nkey, const char *data, ut32 cas);
 SDB_API ut64 sdb_num_nget(Sdb *s, ut64 nkey, ut32 *cas);
 SDB_API int sdb_num_nset(Sdb* s, ut64 nkey, ut64 nval, ut32 cas);
@@ -248,7 +251,9 @@
 SDB_API bool sdb_num_exists(Sdb*, const char *key);
 SDB_API int  sdb_num_base(const char *s);
 SDB_API ut64 sdb_num_get(Sdb* s, const char *key, ut32 *cas);
+SDB_API ut64 sdb_num_getf(Sdb* s, ut32 *cas, const char *fmt, ...) 
SDB_PRINTF_CHECK(3, 4);
 SDB_API int  sdb_num_set(Sdb* s, const char *key, ut64 v, ut32 cas);
+SDB_API int  sdb_num_setf(Sdb* s, ut64 v, ut32 cas, const char *fmt, ...) 
SDB_PRINTF_CHECK(4, 5);
 SDB_API int  sdb_num_add(Sdb *s, const char *key, ut64 v, ut32 cas);
 SDB_API ut64 sdb_num_inc(Sdb* s, const char *key, ut64 n, ut32 cas);
 SDB_API ut64 sdb_num_dec(Sdb* s, const char *key, ut64 n, ut32 cas);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/include/sdb/version.h 
new/sdb-2.5.0/include/sdb/version.h
--- old/sdb-2.4.2/include/sdb/version.h 2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/include/sdb/version.h 2026-08-07 16:51:53.000000000 +0200
@@ -1 +1 @@
-#define SDB_VERSION "2.4.2"
+#define SDB_VERSION "2.5.0"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/memcache/Makefile 
new/sdb-2.5.0/memcache/Makefile
--- old/sdb-2.4.2/memcache/Makefile     2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/memcache/Makefile     2026-08-07 16:51:53.000000000 +0200
@@ -2,6 +2,7 @@
 
 BIN=mcsdbd
 CBIN=mcsdbc
+NETTEST=test_net
 OBJS=main.o memcache.o net.o protocol.o client.o
 COBJS=client.o net.o memcache.o
 LIBS=../src/libsdb.a
@@ -30,7 +31,7 @@
 endif
        ${CC} mainclient.o ${COBJS} ${LDFLAGS} -o ${CBIN} ${LIBS}
 
-${OBJS} mainclient.o: cmds.h
+${OBJS} mainclient.o test_net.o: cmds.h
 
 ${BIN}: ${OBJS}
 ifneq ($(SILENT),)
@@ -38,18 +39,24 @@
 endif
        ${CC} -o ${BIN} $(CFLAGS) ${OBJS} ${LDFLAGS} ${LIBS}
 
-cmds.h: cmds.o
-       ${HOST_CC} -o cmds ../src/util.c cmds.c ${CPPFLAGS} $(CFLAGS) 
${HOST_CFLAGS} ${LDFLAGS}
+cmds.h: cmds.c
+       ${HOST_CC} -o cmds cmds.c ${CPPFLAGS} $(CFLAGS) ${HOST_CFLAGS} 
${LDFLAGS}
        ./cmds > cmds.h
 
+${NETTEST}: test_net.o net.o
+       ${CC} test_net.o net.o ${LDFLAGS} -o ${NETTEST}
+
+check: ${NETTEST}
+       ./${NETTEST}
+
 clean:
        rm -rf *.dSYM
-       rm -f *.sdb ${LIBA} ${COBJS} ${CBIN} mainclient.o
-       rm -f ${OBJS} ${BIN}cmds.h cmds cmds.o
+       rm -f *.sdb ${LIBA} ${COBJS} ${CBIN} mainclient.o ${NETTEST} test_net.o
+       rm -f ${OBJS} ${BIN} cmds.h cmds cmds.o
 
 test:
        ./mcsdbd &
        ./mcsdbc < test
        killall mcsdbd
 
-.PHONY: clean test all loc
+.PHONY: clean test check all loc
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/memcache/main.c 
new/sdb-2.5.0/memcache/main.c
--- old/sdb-2.4.2/memcache/main.c       2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/memcache/main.c       2026-08-07 16:51:53.000000000 +0200
@@ -114,6 +114,12 @@
                        c->idx = 0;
                r = 0;
                rlen = c->len-c->idx;
+               if (rlen > MCSDB_MAX_BUFFER - 1 - c->idx) {
+                       *c->buf = 0;
+                       c->idx = 0;
+                       c->len = 0;
+                       return 0;
+               }
                if (rlen>0 && c->len>0) {
                        r = read (c->fd, c->buf+c->idx, rlen);
                        if (r==-1) {
@@ -133,7 +139,7 @@
                        }// else eprintf ("Invalid %d read wtf\n", r);
                } 
                c->idx += r;
-               c->buf[c->idx+1] = 0;
+               c->buf[c->idx] = 0;
                if (c->idx == c->len) {
                //      printf ("END OF MODE 1 ***/*/*///*/* ((%s))\n", c->buf);
                        return 1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/memcache/net.c new/sdb-2.5.0/memcache/net.c
--- old/sdb-2.4.2/memcache/net.c        2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/memcache/net.c        2026-08-07 16:51:53.000000000 +0200
@@ -2,18 +2,43 @@
 
 #include "mcsdb.h"
 #include <sdb/types.h>
+#include <errno.h>
 #include <netinet/in.h>
 #include <fcntl.h>
 #include <poll.h>
 #include <netdb.h>
 #include <signal.h>
 #include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 #include <sys/socket.h>
 #include <stdarg.h>
+#include <unistd.h>
 
 static char netbuf[MCSDB_MAX_BUFFER];
 static size_t netbuflen = 0;
 
+static int net_write_all(int fd, const char *buf, size_t len) {
+       size_t written = 0;
+       if (fd == -1) {
+               fd = STDOUT_FILENO;
+       }
+       while (written < len) {
+               ssize_t n = write (fd, buf + written, len - written);
+               if (n < 0) {
+                       if (errno == EINTR) {
+                               continue;
+                       }
+                       return -1;
+               }
+               if (!n) {
+                       return -1;
+               }
+               written += n;
+       }
+       return 0;
+}
+
 char *net_readnl(int fd) {
        int i = -1;
        char buf[MCSDB_MAX_BUFFER];
@@ -33,33 +58,69 @@
 //NetSocket *net_accept(NetSocket *s) { }
 
 int net_flush(int fd) {
-       int n;
+       size_t len;
        if (netbuflen < 1) {
                return 0;
        }
-       if (fd == -1) {
-               fd = 1; //stdout
+       len = netbuflen;
+       if (net_write_all (fd, netbuf, len) < 0) {
+               netbuflen = 0;
+               return -1;
        }
-       n = write (fd, netbuf, netbuflen);
        netbuflen = 0;
-       return n;
+       return (int)len;
 }
 
 int net_printf (int fd, const char *fmt, ...) {
-       int n;
+       char *out;
+       int n, formatted;
+       size_t len;
        char buf[MCSDB_MAX_BUFFER];
        va_list ap;
+       if (!fmt) {
+               return -1;
+       }
        va_start (ap, fmt);
-       n = vsnprintf (buf, sizeof (buf)-1, fmt, ap);
+       n = vsnprintf (buf, sizeof (buf), fmt, ap);
        va_end (ap);
        if (n < 0) {
-               return 0;
+               return -1;
+       }
+       len = n;
+       out = buf;
+       if (len >= sizeof (buf)) {
+               out = malloc (len + 1);
+               if (!out) {
+                       return -1;
+               }
+               va_start (ap, fmt);
+               formatted = vsnprintf (out, len + 1, fmt, ap);
+               va_end (ap);
+               if (formatted != n) {
+                       free (out);
+                       return -1;
+               }
+       }
+       if (len > sizeof (netbuf)) {
+               if (net_flush (fd) < 0 || net_write_all (fd, out, len) < 0) {
+                       if (out != buf) {
+                               free (out);
+                       }
+                       return -1;
+               }
+       } else {
+               if (len > sizeof (netbuf) - netbuflen && net_flush (fd) < 0) {
+                       if (out != buf) {
+                               free (out);
+                       }
+                       return -1;
+               }
+               memcpy (netbuf + netbuflen, out, len);
+               netbuflen += len;
        }
-       if (netbuflen+n > sizeof (netbuf)) {
-               net_flush (fd);
+       if (out != buf) {
+               free (out);
        }
-       strcpy (netbuf + netbuflen, buf);
-       netbuflen += n;
        return n;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/meson.build new/sdb-2.5.0/meson.build
--- old/sdb-2.4.2/meson.build   2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/meson.build   2026-08-07 16:51:53.000000000 +0200
@@ -1,4 +1,4 @@
-project('sdb', 'c', meson_version : '>=0.60.0', version : '2.4.2')
+project('sdb', 'c', meson_version : '>=0.60.0', version : '2.5.0')
 
 pkgconfig_mod = import('pkgconfig')
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/src/Makefile new/sdb-2.5.0/src/Makefile
--- old/sdb-2.4.2/src/Makefile  2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/src/Makefile  2026-08-07 16:51:53.000000000 +0200
@@ -71,7 +71,8 @@
 ifneq ($(SILENT),)
        @echo BIN ${BIN}
 endif
-       $(CC) ${LDFLAGS} -o ${BIN} entry.o ${OBJ}
+# CFLAGS may force a source language; reset it before passing object files.
+       $(CC) ${CFLAGS} -x none ${LDFLAGS} -o ${BIN} entry.o ${OBJ}
 
 mrproper clean:
        rm -rf ${OBJ} ${SOBJ} entry.o libsdb.a a.out ${BIN} sdb.dSYM
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/src/cdb_make.c new/sdb-2.5.0/src/cdb_make.c
--- old/sdb-2.4.2/src/cdb_make.c        2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/src/cdb_make.c        2026-08-07 16:51:53.000000000 +0200
@@ -122,21 +122,36 @@
        return cdb_make_addend (c, keylen, datalen, sdb_hash (key));
 }
 
+void cdb_make_cancel(struct cdb_make *c) {
+       struct cdb_hplist *x, *n;
+       if (!c) {
+               return;
+       }
+       for (x = c->head; x; x = n) {
+               n = x->next;
+               cdb_alloc_free (x);
+       }
+       cdb_alloc_free (c->split);
+       c->head = NULL;
+       c->split = NULL;
+       c->hash = NULL;
+}
+
 int cdb_make_finish(struct cdb_make *c) {
-       int i;
+       int i, result;
        char buf[8];
        struct cdb_hp *hp;
-       struct cdb_hplist *x, *n;
+       struct cdb_hplist *x;
        ut32 len, u, memsize, count, where;
 
        memsize = c->memsize + c->numentries;
        if (memsize > (UT32_MAX / sizeof (struct cdb_hp))) {
-               return 0;
+               goto fail;
        }
        // Allocate memory with proper alignment
        c->split = (struct cdb_hp *) cdb_alloc (memsize * sizeof (struct 
cdb_hp));
        if (!c->split) {
-               return 0;
+               goto fail;
        }
        // Ensure the pointer arithmetic is done with proper type safety
        // by first casting to char* for byte-wise arithmetic, then back to 
struct cdb_hp*
@@ -175,26 +190,25 @@
                        ut32_pack (buf, c->hash[u].h);
                        ut32_pack (buf + 4, c->hash[u].p);
                        if (!buffer_putalign (&c->b, buf, 8)) {
-                               return 0;
+                               goto fail;
                        }
                        if (!incpos (c, 8)) {
-                               return 0;
+                               goto fail;
                        }
                }
        }
 
        if (!buffer_flush (&c->b)) {
-               return 0;
+               goto fail;
        }
        if (!seek_set (c->fd, 0)) {
-               return 0;
-       }
-       // free childs
-       for (x = c->head; x;) {
-               n = x->next;
-               cdb_alloc_free (x);
-               x = n;
+               goto fail;
        }
-       cdb_alloc_free (c->split);
-       return buffer_putflush (&c->b, c->final, sizeof c->final);
+       result = buffer_putflush (&c->b, c->final, sizeof c->final);
+       cdb_make_cancel (c);
+       return result;
+
+fail:
+       cdb_make_cancel (c);
+       return 0;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/src/disk.c new/sdb-2.5.0/src/disk.c
--- old/sdb-2.4.2/src/disk.c    2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/src/disk.c    2026-08-07 16:51:53.000000000 +0200
@@ -3,7 +3,7 @@
 #include <fcntl.h>
 #include <errno.h>
 #include <sys/stat.h>
-#include "sdb/sdb.h"
+#include "sdb_private.h"
 
 #if __SDB_WINDOWS__
 
@@ -49,6 +49,20 @@
 #define r_sys_mkdir_failed() (errno != EEXIST)
 #endif
 
+static bool unlink_path(const char *path) {
+       if (!path) {
+               return false;
+       }
+#if __SDB_WINDOWS__ && UNICODE
+       wchar_t *wpath = r_sys_conv_utf8_to_utf16 (path);
+       bool ret = wpath && _wunlink (wpath) != -1;
+       sdb_gh_free (wpath);
+       return ret;
+#else
+       return unlink (path) != -1;
+#endif
+}
+
 static inline bool mkdirp(char *dir) {
        const char slash = DIRSEP;
        char *path = dir;
@@ -95,9 +109,6 @@
        memcpy (str, dir, nlen + 1);
        mkdirp (str);
        memcpy (str + nlen, ".tmp", 5);
-       if (s->fdump != -1) {
-               close (s->fdump);
-       }
 #if __SDB_WINDOWS__ && UNICODE
        wchar_t *wstr = r_sys_conv_utf8_to_utf16 (str);
        if (wstr) {
@@ -114,61 +125,99 @@
                sdb_gh_free (str);
                return false;
        }
-       cdb_make_start (&s->m, s->fdump);
        s->ndump = str;
+       if (!cdb_make_start (&s->m, s->fdump)) {
+               sdb_disk_abort (s);
+               return false;
+       }
        return true;
 }
 
 SDB_API bool sdb_disk_insert(Sdb* s, const char *key, const char *val) {
-       struct cdb_make *c = &s->m;
-       if (!key || !val) {
+       if (!s || !key || !val) {
                return false;
        }
        //if (!*val) return 0; //undefine variable if no value
-       return cdb_make_add (c, key, strlen (key), val, strlen (val));
+       return cdb_make_add (&s->m, key, strlen (key), val, strlen (val));
+}
+
+static void close_current_database(Sdb *s) {
+       cdb_free (&s->db);
+       s->db.fd = -1;
+       if (s->fd != -1) {
+               close (s->fd);
+               s->fd = -1;
+       }
+}
+
+SDB_IPI void sdb_disk_abort(Sdb *s) {
+       if (!s) {
+               return;
+       }
+       cdb_make_cancel (&s->m);
+       if (s->fdump >= 0) {
+               close (s->fdump);
+       }
+       s->fdump = -1;
+       if (s->ndump) {
+               unlink_path (s->ndump);
+               R_FREE (s->ndump);
+       }
 }
 
-#define IFRET(x) if (x) ret = 0
 SDB_API bool sdb_disk_finish(Sdb* s) {
-       bool ret = true;
-       IFRET (!cdb_make_finish (&s->m));
+       if (!s) {
+               return false;
+       }
+       if (s->fdump < 0 || !s->ndump || !s->dir) {
+               sdb_disk_abort (s);
+               return false;
+       }
+       bool ret = cdb_make_finish (&s->m);
 #if USE_MMAN
-       IFRET (fsync (s->fdump));
+       if (ret && fsync (s->fdump) == -1) {
+               ret = false;
+       }
 #endif
-       IFRET (close (s->fdump));
+       if (close (s->fdump) == -1) {
+               ret = false;
+       }
        s->fdump = -1;
-       // close current fd to avoid sharing violations
-       if (s->fd != -1) {
-               close (s->fd);
-               s->fd = -1;
+       if (!ret) {
+               sdb_disk_abort (s);
+               return false;
        }
 #if __SDB_WINDOWS__
        LPTSTR ndump_ = r_sys_conv_utf8_to_utf16 (s->ndump);
        LPTSTR dir_ = r_sys_conv_utf8_to_utf16 (s->dir);
-
-       if (MoveFileEx (ndump_, dir_, MOVEFILE_REPLACE_EXISTING)) {
-               //eprintf ("Error 0x%02x\n", GetLastError ());
+       if (!ndump_ || !dir_) {
+               sdb_gh_free (ndump_);
+               sdb_gh_free (dir_);
+               sdb_disk_abort (s);
+               return false;
        }
+       // Windows cannot replace a file while the existing database is open.
+       close_current_database (s);
+       ret = MoveFileEx (ndump_, dir_, MOVEFILE_REPLACE_EXISTING) != 0;
        sdb_gh_free (ndump_);
        sdb_gh_free (dir_);
+       if (!ret) {
+               sdb_disk_abort (s);
+               (void)sdb_open (s, s->dir);
+               return false;
+       }
 #else
-       if (s->ndump && s->dir) {
-               IFRET (rename (s->ndump, s->dir));
+       if (rename (s->ndump, s->dir) == -1) {
+               sdb_disk_abort (s);
+               return false;
        }
+       close_current_database (s);
 #endif
        sdb_gh_free (s->ndump);
        s->ndump = NULL;
-       // always reopen just in case
-       {
-               int rr = sdb_open (s, s->dir);
-               if (ret && rr < 0) {
-                       ret = false;
-               }
-               cdb_init (&s->db, s->fd);
-       }
-       return ret;
+       return sdb_open (s, s->dir) >= 0;
 }
 
 SDB_API bool sdb_disk_unlink(Sdb *s) {
-       return (s->dir && *(s->dir) && unlink (s->dir) != -1);
+       return s->dir && *s->dir && unlink_path (s->dir);
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/src/ht.inc.c new/sdb-2.5.0/src/ht.inc.c
--- old/sdb-2.4.2/src/ht.inc.c  2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/src/ht.inc.c  2026-08-07 16:51:53.000000000 +0200
@@ -198,7 +198,7 @@
        }
 
        if (bt->count + 1 >= bt->size) {
-               bt->size = (bt->count + 5) * 2;
+               bt->size = (bt->count + 1) * 2;
                HT_(Kv) *newkvarr = (HT_(Kv)*)sdb_gh_realloc (bt->arr, 
(bt->size) * ht->opt.elem_size);
                if (!SDB_UNLIKELY (newkvarr)) {
                        return NULL;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/src/ns.c new/sdb-2.5.0/src/ns.c
--- old/sdb-2.4.2/src/ns.c      2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/src/ns.c      2026-08-07 16:51:53.000000000 +0200
@@ -59,7 +59,7 @@
                                        sdb_free (child);
                                }
                        }
-                       free (ns->name);
+                       sdb_gh_free (ns->name);
                        ns->name = NULL;
                }
                if (!deleted) {
@@ -76,10 +76,10 @@
                        }
                        s->ns->free = NULL;
                        ls_delete (s->ns, it); // free (it)
-                       free (ns->name);
+                       sdb_gh_free (ns->name);
                        ns->name = NULL;
                }
-               free (ns);
+               sdb_gh_free (ns);
                it = &next;
        }
        ls_free (s->ns);
@@ -118,8 +118,8 @@
                                sdb_free (ns->sdb);
                        }
                }
-               free (ns->name);
-               free (ns);
+               sdb_gh_free (ns->name);
+               sdb_gh_free (ns);
        }
        ls_free (s->ns);
        s->ns = NULL;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/src/num.c new/sdb-2.5.0/src/num.c
--- old/sdb-2.4.2/src/num.c     2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/src/num.c     2026-08-07 16:51:53.000000000 +0200
@@ -1,6 +1,6 @@
 /* sdb - MIT - Copyright 2011-2022 - pancake */
 
-#include "sdb/sdb.h"
+#include "sdb_private.h"
 
 // check if key exists and if it's a number.. rename?
 SDB_API bool sdb_num_exists (Sdb *s, const char *key) {
@@ -8,11 +8,22 @@
        return o ? (*o >= '0' && *o <= '9'): false;
 }
 
-SDB_API ut64 sdb_num_get(Sdb *s, const char *key, ut32 *cas) {
-       const char *v = sdb_const_get (s, key, cas);
+static ut64 sdb_num_parse(const char *v) {
        return (!v || *v == '-') ? 0LL : sdb_atoi (v);
 }
 
+SDB_API ut64 sdb_num_get(Sdb *s, const char *key, ut32 *cas) {
+       return sdb_num_parse (sdb_const_get (s, key, cas));
+}
+
+SDB_API ut64 sdb_num_getf(Sdb *s, ut32 *cas, const char *fmt, ...) {
+       va_list ap;
+       va_start (ap, fmt);
+       ut64 value = sdb_num_parse (sdb_const_vgetf (s, cas, fmt, ap));
+       va_end (ap);
+       return value;
+}
+
 SDB_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));
@@ -27,6 +38,22 @@
        return sdb_set (s, key, val, cas);
 }
 
+SDB_API int sdb_num_setf(Sdb *s, ut64 v, ut32 cas, const char *fmt, ...) {
+       va_list ap;
+       char key[SDB_MAX_KEY];
+       char val[SDB_NUM_BUFSZ];
+       int len, numbase;
+
+       va_start (ap, fmt);
+       len = vsnprintf (key, sizeof (key), fmt, ap);
+       va_end (ap);
+       if (len < 0 || len >= (int)sizeof (key)) {
+               return 0;
+       }
+       numbase = sdb_num_base (sdb_const_get (s, key, NULL));
+       return sdb_set (s, key, sdb_itoa (v, numbase, val, sizeof (val)), cas);
+}
+
 SDB_API ut64 sdb_num_inc(Sdb *s, const char *key, ut64 n2, ut32 cas) {
        ut32 c;
        ut64 n = sdb_num_get (s, key, &c);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/src/sdb.c new/sdb-2.5.0/src/sdb.c
--- old/sdb-2.4.2/src/sdb.c     2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/src/sdb.c     2026-08-07 16:51:53.000000000 +0200
@@ -3,7 +3,7 @@
 #include <fcntl.h>
 #include <errno.h>
 #include <sys/stat.h>
-#include "sdb/sdb.h"
+#include "sdb_private.h"
 
 static inline SdbKv *next_kv(HtPP *ht, SdbKv *kv) {
        return (SdbKv *)((uintptr_t)kv + ht->opt.elem_size);
@@ -182,11 +182,11 @@
        }
        sdb_ns_free (s);
        s->refs = 0;
+       sdb_journal_close (s);
        sdb_gh_free (s->name);
        sdb_gh_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;
@@ -284,7 +284,27 @@
        return sdb_const_get_len (s, key, NULL, cas);
 }
 
-// TODO: add sdb_getf?
+SDB_IPI const char *sdb_const_vgetf(Sdb *s, ut32 *cas, const char *fmt, 
va_list ap) {
+       if (fmt) {
+               char key[SDB_MAX_KEY];
+               int len = vsnprintf (key, SDB_MAX_KEY, fmt, ap);
+               if (len >= 0 && len < SDB_MAX_KEY) {
+                       return sdb_const_get (s, key, cas);
+               }
+       }
+       if (cas) {
+               *cas = 0;
+       }
+       return NULL;
+}
+
+SDB_API const char *sdb_const_getf(Sdb *s, ut32 *cas, const char *fmt, ...) {
+       va_list ap;
+       va_start (ap, fmt);
+       const char *value = sdb_const_vgetf (s, cas, fmt, ap);
+       va_end (ap);
+       return value;
+}
 
 SDB_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);
@@ -309,9 +329,89 @@
        return sdb_nset (s, nkey, "", cas);
 }
 
-/* remove from memory */
-SDB_API bool sdb_remove(Sdb *s, const char *key, ut32 cas) {
-       return sdb_ht_delete (s->ht, key);
+SDB_API bool sdb_rename(Sdb *s, const char *oldkey, const char *newkey, ut32 
cas) {
+       if (!s || !oldkey || !newkey) {
+               return false;
+       }
+       char *value = sdb_get (s, oldkey, NULL);
+       if (!value) {
+               return false;
+       }
+       if (!sdb_unset (s, oldkey, cas)) {
+               sdb_gh_free (value);
+               return false;
+       }
+       return sdb_set_owned (s, newkey, value, 0) != 0;
+}
+
+typedef struct {
+       const char *prefix;
+       size_t len;
+       SdbList *keys;
+} RenamePrefixData;
+
+static bool rename_prefix_cb(void *user, const char *k, const char *v) {
+       RenamePrefixData *rpd = (RenamePrefixData *)user;
+       (void)v;
+       if (strncmp (k, rpd->prefix, rpd->len)) {
+               return true;
+       }
+       char *key = sdb_strdup (k);
+       if (!key) {
+               return false;
+       }
+       if (!ls_append (rpd->keys, key)) {
+               sdb_gh_free (key);
+               return false;
+       }
+       return true;
+}
+
+static char *rename_prefix_key(const char *newprefix, const char *suffix) {
+       size_t plen = strlen (newprefix);
+       size_t slen = strlen (suffix);
+       if (SZT_ADD_OVFCHK (plen, slen) || SZT_ADD_OVFCHK (plen + slen, 1)) {
+               return NULL;
+       }
+       char *key = (char *)sdb_gh_malloc (plen + slen + 1);
+       if (key) {
+               memcpy (key, newprefix, plen);
+               memcpy (key + plen, suffix, slen + 1);
+       }
+       return key;
+}
+
+SDB_API int sdb_rename_prefix(Sdb *s, const char *oldprefix, const char 
*newprefix) {
+       if (!s || !oldprefix || !newprefix) {
+               return 0;
+       }
+       SdbList *keys = ls_newf ((SdbListFree)sdb_gh_free);
+       if (!keys) {
+               return 0;
+       }
+       RenamePrefixData rpd = {
+               .prefix = oldprefix,
+               .len = strlen (oldprefix),
+               .keys = keys
+       };
+       if (!sdb_foreach (s, rename_prefix_cb, &rpd)) {
+               ls_free (keys);
+               return 0;
+       }
+       int count = 0;
+       SdbListIter *iter;
+       char *key;
+       ls_foreach_cast (keys, iter, char *, key) {
+               char *newkey = rename_prefix_key (newprefix, key + rpd.len);
+               if (newkey) {
+                       if (sdb_rename (s, key, newkey, 0)) {
+                               count++;
+                       }
+                       sdb_gh_free (newkey);
+               }
+       }
+       ls_free (keys);
+       return count;
 }
 
 // alias for '-key=str'.. '+key=str' concats
@@ -606,6 +706,7 @@
        cdb_findstart (&s->db);
        SdbKv *kv = sdb_ht_find_kvp (s->ht, key, &found);
        if (found && sdbkv_value (kv)) {
+               bool free_val = owned;
                if (cdb_findnext (&s->db, sdb_hash (key), key, klen)) {
                        if (cas && kv->cas != cas) {
                                if (owned) {
@@ -625,6 +726,7 @@
                                kv->base.value_len = vlen;
                                sdb_gh_free (kv->base.value);
                                kv->base.value = val; // owned
+                               free_val = false;
                        } else {
                                if ((ut32)vlen > kv->base.value_len) {
                                        sdb_gh_free (kv->base.value);
@@ -634,9 +736,13 @@
                                kv->base.value_len = vlen;
                        }
                } else {
+                       cas = nextcas (kv);
                        sdb_ht_delete (s->ht, key);
                }
                sdb_hook_call (s, key, val);
+               if (free_val) {
+                       sdb_gh_free (val);
+               }
                return cas;
        }
        // empty values are also stored
@@ -669,6 +775,16 @@
        return sdb_set_internal (s, key, (char *)val, false, cas);
 }
 
+SDB_API int sdb_setf(Sdb *s, const char *val, ut32 cas, const char *fmt, ...) {
+       va_list ap;
+       char key[SDB_MAX_KEY];
+       int len;
+       va_start (ap, fmt);
+       len = vsnprintf (key, sizeof (key), fmt, ap);
+       va_end (ap);
+       return len >= 0 && len < (int)sizeof (key)? sdb_set (s, key, val, cas): 
0;
+}
+
 SDB_API int sdb_nset(Sdb* s, ut64 nkey, const char *val, ut32 cas) {
        char buf[SDB_NUM_BUFSZ];
        const char *key = sdb_itoa (nkey, 16, buf, sizeof (buf));
@@ -864,35 +980,57 @@
        return sdb_foreach_end (s, true);
 }
 
+typedef struct {
+       Sdb *s;
+       HtPP *persisted;
+} SdbSync;
+
+static bool _sync_has_persisted(SdbSync *sync, const char *key) {
+       return ht_pp_find (sync->persisted, key, NULL) != NULL;
+}
+
 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;
+       SdbSync *sync = (SdbSync *)user;
+       if (!sync || !sync->s) {
+               return false;
        }
-       return false;
+       // An updated key may occur more than once in an existing CDB.
+       return _sync_has_persisted (sync, key)
+               || sdb_disk_insert (sync->s, key, value);
+}
+
+static bool _mark_persisted(void *user, const char *key, const char *value) {
+       SdbSync *sync = (SdbSync *)user;
+       (void)value;
+       return sync && (_sync_has_persisted (sync, key)
+               || ht_pp_insert (sync->persisted, key, (void *)1));
 }
 
-static bool _remove_afer_insert(void *user, const char *k, const char *v) {
+static bool _remove_persisted(void *user, const void *key, const void *value) {
        Sdb *s = (Sdb *)user;
-       if (s) {
-               sdb_ht_delete (s->ht, k);
-               return true;
-       }
-       return false;
+       (void)value;
+       (void)sdb_ht_delete (s->ht, (const char *)key);
+       return true;
 }
 
 SDB_API bool sdb_sync(Sdb* s) {
-       bool result;
        ut32 i;
 
-       if (!s || !sdb_disk_create (s)) {
+       if (!s) {
                return false;
        }
-       result = sdb_foreach_cdb (s, _insert_into_disk, _remove_afer_insert, s);
-       if (!result) {
+       HtPP *persisted = ht_pp_new0 ();
+       if (!persisted) {
                return false;
        }
+       SdbSync sync = { s, persisted };
+       if (!sdb_disk_create (s)) {
+               ht_pp_free (persisted);
+               return false;
+       }
+       if (!sdb_foreach_cdb (s, _insert_into_disk, _mark_persisted, &sync)) {
+               goto fail;
+       }
 
        /* append new keyvalues */
        for (i = 0; i < s->ht->size; i++) {
@@ -903,18 +1041,29 @@
                BUCKET_FOREACH_SAFE (s->ht, bt, j, count, kv) {
                        if (sdbkv_key (kv)) {
                                const char *kvv = sdbkv_value (kv);
-                               if (kvv && *kvv && !kv->expire) {
-                                       if (sdb_disk_insert (s, sdbkv_key (kv), 
sdbkv_value (kv))) {
-                                               sdb_remove (s, sdbkv_key (kv), 
0);
+                               if (kvv && *kvv && !kv->expire
+                                       && !_sync_has_persisted (&sync, 
sdbkv_key (kv))) {
+                                       if (!sdb_disk_insert (s, sdbkv_key 
(kv), kvv)
+                                               || !_mark_persisted (&sync, 
sdbkv_key (kv), kvv)) {
+                                               goto fail;
                                        }
                                }
                        }
                }
        }
-       sdb_disk_finish (s);
-       sdb_journal_clear (s);
-       // TODO: sdb_reset memory state?
+       if (!sdb_disk_finish (s)) {
+               goto fail;
+       }
+       // Dirty entries remain available for retry until the replacement 
succeeds.
+       ht_pp_foreach (persisted, _remove_persisted, s);
+       ht_pp_free (persisted);
+       (void)sdb_journal_clear (s);
        return true;
+
+fail:
+       sdb_disk_abort (s);
+       ht_pp_free (persisted);
+       return false;
 }
 
 SDB_API void sdb_dump_begin(Sdb* s) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/src/sdb_private.h 
new/sdb-2.5.0/src/sdb_private.h
--- old/sdb-2.4.2/src/sdb_private.h     1970-01-01 01:00:00.000000000 +0100
+++ new/sdb-2.5.0/src/sdb_private.h     2026-08-07 16:51:53.000000000 +0200
@@ -0,0 +1,10 @@
+#ifndef SDB_PRIVATE_H
+#define SDB_PRIVATE_H
+
+#include <stdarg.h>
+#include "sdb/sdb.h"
+
+SDB_IPI const char *sdb_const_vgetf(Sdb *s, ut32 *cas, const char *fmt, 
va_list ap);
+SDB_IPI void sdb_disk_abort(Sdb *s);
+
+#endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/src/tool.c new/sdb-2.5.0/src/tool.c
--- old/sdb-2.4.2/src/tool.c    2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/src/tool.c    2026-08-07 16:51:53.000000000 +0200
@@ -202,9 +202,9 @@
                                        strbuf_appendf (sb, 0, "\t{\"%s\", 
\"%s\"},\n", ev, ek);
                                }
                        }
-                       sdb_gh_free (ek);
-                       sdb_gh_free (ev);
                }
+               sdb_gh_free (ek);
+               sdb_gh_free (ev);
        }
        ls_free (l);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/test/unit/meson.build 
new/sdb-2.5.0/test/unit/meson.build
--- old/sdb-2.4.2/test/unit/meson.build 2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/test/unit/meson.build 2026-08-07 16:51:53.000000000 +0200
@@ -5,7 +5,8 @@
     'diff',
     'hash',
     'ls',
-    'sdb'
+    'sdb',
+    'sync'
   ]
 
   foreach test : tests
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/test/unit/test_getf.c 
new/sdb-2.5.0/test/unit/test_getf.c
--- old/sdb-2.4.2/test/unit/test_getf.c 1970-01-01 01:00:00.000000000 +0100
+++ new/sdb-2.5.0/test/unit/test_getf.c 2026-08-07 16:51:53.000000000 +0200
@@ -0,0 +1,47 @@
+#include "minunit.h"
+#include <sdb/sdb.h>
+
+static bool test_getf(void) {
+       Sdb *db = sdb_new0 ();
+       mu_assert_notnull (db, "database");
+       mu_assert ("set", sdb_set (db, "user.alice.42", "admin", 0));
+       mu_assert ("formatted set", sdb_setf (db, "staff", 0, "group.%s.%d", 
"alice", 42));
+       mu_assert_streq (sdb_const_get (db, "group.alice.42", NULL), "staff", 
"formatted set value");
+       mu_assert ("formatted number set", sdb_num_setf (db, 42, 0, 
"number.%s", "answer"));
+       mu_assert_eq (sdb_num_get (db, "number.answer", NULL), 42, "formatted 
number set value");
+
+       ut32 const_cas = 0;
+       const char *const_value = sdb_const_getf (db, &const_cas,
+               "user.%s.%d", "alice", 42);
+       mu_assert_streq (const_value, "admin", "formatted const get");
+       mu_assert ("const get CAS", const_cas != 0);
+
+       char maxkey[SDB_MAX_KEY];
+       memset (maxkey, 'x', sizeof (maxkey) - 1);
+       maxkey[sizeof (maxkey) - 1] = 0;
+       mu_assert ("set maximum key", sdb_set (db, maxkey, "maximum", 0));
+       mu_assert_streq (sdb_const_getf (db, NULL, "%s", maxkey), "maximum",
+               "maximum formatted key");
+
+       char oversized[SDB_MAX_KEY + 1];
+       memset (oversized, 'x', sizeof (oversized) - 1);
+       oversized[sizeof (oversized) - 1] = 0;
+       ut32 cas = 123;
+       mu_assert_null (sdb_const_getf (db, &cas, "%s", oversized),
+               "oversized formatted key");
+       mu_assert_eq (cas, 0, "oversized key CAS");
+       mu_assert_null (sdb_const_getf (db, NULL, "missing.%d", 7),
+               "missing formatted key");
+
+       sdb_free (db);
+       mu_end;
+}
+
+int all_tests(void) {
+       mu_run_test (test_getf);
+       return tests_passed != tests_run;
+}
+
+int main(void) {
+       return all_tests ();
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/test/unit/test_sdb.c 
new/sdb-2.5.0/test/unit/test_sdb.c
--- old/sdb-2.4.2/test/unit/test_sdb.c  2026-03-11 20:13:35.000000000 +0100
+++ new/sdb-2.5.0/test/unit/test_sdb.c  2026-08-07 16:51:53.000000000 +0200
@@ -105,6 +105,34 @@
        mu_end;
 }
 
+bool test_sdb_unset_return(void) {
+       Sdb *db = sdb_new0 ();
+       sdb_set (db, "foo", "bar", 0);
+       int ret = sdb_unset (db, "foo", 0);
+       mu_assert ("Unset failed", ret != 0);
+       mu_assert ("Item not deleted", !sdb_exists (db, "foo"));
+       sdb_free (db);
+       mu_end;
+}
+
+bool test_sdb_rename_prefix(void) {
+       Sdb *db = sdb_new0 ();
+       sdb_set (db, "zign|old|foo", "aaa", 0);
+       sdb_set (db, "zign|old|bar", "bbb", 0);
+       sdb_set (db, "zign|keep|foo", "ccc", 0);
+
+       int ret = sdb_rename_prefix (db, "zign|old|", "zign|new|");
+       mu_assert_eq (ret, 2, "Renamed keys");
+       mu_assert ("Old foo still exists", !sdb_exists (db, "zign|old|foo"));
+       mu_assert ("Old bar still exists", !sdb_exists (db, "zign|old|bar"));
+       mu_assert ("New foo missing", !strcmp (sdb_const_get (db, 
"zign|new|foo", NULL), "aaa"));
+       mu_assert ("New bar missing", !strcmp (sdb_const_get (db, 
"zign|new|bar", NULL), "bbb"));
+       mu_assert ("Unmatched key changed", !strcmp (sdb_const_get (db, 
"zign|keep|foo", NULL), "ccc"));
+
+       sdb_free (db);
+       mu_end;
+}
+
 bool test_sdb_milset(void) {
        int i = 0;
        const int MAX = 19999999;
@@ -530,6 +558,8 @@
        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_unset_return);
+       mu_run_test (test_sdb_rename_prefix);
        mu_run_test (test_sdb_milset);
        mu_run_test (test_sdb_milset_random);
        mu_run_test (test_sdb_list_big);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/sdb-2.4.2/test/unit/test_sync.c 
new/sdb-2.5.0/test/unit/test_sync.c
--- old/sdb-2.4.2/test/unit/test_sync.c 1970-01-01 01:00:00.000000000 +0100
+++ new/sdb-2.5.0/test/unit/test_sync.c 2026-08-07 16:51:53.000000000 +0200
@@ -0,0 +1,120 @@
+#include "minunit.h"
+#include <sdb/sdb.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#if !__SDB_WINDOWS__
+#include <signal.h>
+#include <sys/resource.h>
+
+static void cleanup_sync_files(const char *path) {
+       char file[SDB_MAX_PATH];
+       unlink (path);
+       snprintf (file, sizeof (file), "%s.tmp", path);
+       unlink (file);
+       snprintf (file, sizeof (file), "%s.journal", path);
+       unlink (file);
+}
+
+static bool database_value_is(const char *path, const char *key, const char 
*expected) {
+       Sdb *db = sdb_new (NULL, path, 0);
+       if (!db) {
+               return false;
+       }
+       const char *value = sdb_const_get (db, key, NULL);
+       bool result = expected? value && !strcmp (value, expected): !value;
+       sdb_free (db);
+       return result;
+}
+#endif
+
+static bool test_sync_failure_preserves_database(void) {
+#if __SDB_WINDOWS__
+       mu_ignore;
+#else
+       const char *path = ".test-sync-failure.sdb";
+       char tmp[SDB_MAX_PATH];
+       struct rlimit saved_limit, failing_limit;
+       struct sigaction saved_action, ignored_action;
+       struct stat journal_stat;
+       cleanup_sync_files (path);
+
+       Sdb *db = sdb_new (NULL, path, 0);
+       bool initial_ok = db
+               && sdb_set (db, "stable", "old", 0)
+               && sdb_sync (db)
+               && sdb_journal_open (db);
+       if (!initial_ok) {
+               sdb_free (db);
+               cleanup_sync_files (path);
+               mu_fail ("initial database setup failed");
+       }
+       bool updates_ok = sdb_set (db, "stable", "new", 0)
+               && sdb_set (db, "pending", "value", 0);
+       if (!updates_ok || getrlimit (RLIMIT_FSIZE, &saved_limit) == -1) {
+               sdb_free (db);
+               cleanup_sync_files (path);
+               mu_fail ("failed to prepare sync failure");
+       }
+
+       memset (&ignored_action, 0, sizeof (ignored_action));
+       ignored_action.sa_handler = SIG_IGN;
+       sigemptyset (&ignored_action.sa_mask);
+       if (sigaction (SIGXFSZ, &ignored_action, &saved_action) == -1) {
+               sdb_free (db);
+               cleanup_sync_files (path);
+               mu_fail ("failed to ignore SIGXFSZ");
+       }
+       failing_limit = saved_limit;
+       failing_limit.rlim_cur = 0;
+       if (setrlimit (RLIMIT_FSIZE, &failing_limit) == -1) {
+               sigaction (SIGXFSZ, &saved_action, NULL);
+               sdb_free (db);
+               cleanup_sync_files (path);
+               mu_fail ("failed to limit output size");
+       }
+
+       bool sync_result = sdb_sync (db);
+       bool limit_restored = setrlimit (RLIMIT_FSIZE, &saved_limit) != -1;
+       bool action_restored = sigaction (SIGXFSZ, &saved_action, NULL) != -1;
+       const char *stable = sdb_const_get (db, "stable", NULL);
+       const char *pending = sdb_const_get (db, "pending", NULL);
+       bool memory_retained = stable && pending
+               && !strcmp (stable, "new") && !strcmp (pending, "value");
+       bool journal_retained = fstat (db->journal, &journal_stat) != -1
+               && journal_stat.st_size > 0;
+       bool disk_preserved = database_value_is (path, "stable", "old")
+               && database_value_is (path, "pending", NULL);
+       snprintf (tmp, sizeof (tmp), "%s.tmp", path);
+       bool temp_removed = access (tmp, F_OK) == -1;
+
+       bool retry_result = sdb_sync (db);
+       bool disk_updated = database_value_is (path, "stable", "new")
+               && database_value_is (path, "pending", "value");
+       bool journal_cleared = fstat (db->journal, &journal_stat) != -1
+               && journal_stat.st_size == 0;
+
+       sdb_free (db);
+       cleanup_sync_files (path);
+       mu_assert_false (sync_result, "failed sync result");
+       mu_assert_true (limit_restored, "restore file-size limit");
+       mu_assert_true (action_restored, "restore SIGXFSZ handler");
+       mu_assert_true (memory_retained, "pending changes retained in memory");
+       mu_assert_true (journal_retained, "journal retained after failure");
+       mu_assert_true (disk_preserved, "existing database preserved");
+       mu_assert_true (temp_removed, "failed temporary database removed");
+       mu_assert_true (retry_result, "retry succeeds");
+       mu_assert_true (disk_updated, "retry persists pending changes");
+       mu_assert_true (journal_cleared, "journal cleared after success");
+       mu_end;
+#endif
+}
+
+static int all_tests(void) {
+       mu_run_test (test_sync_failure_preserves_database);
+       return tests_passed != tests_run;
+}
+
+int main(int argc, char **argv) {
+       return all_tests ();
+}

Reply via email to