From 6489957449fec63ddf330330e9435b4ee0c388b0 Mon Sep 17 00:00:00 2001
From: Michael Schroeder <mls@suse.de>
Date: Mon, 3 Feb 2020 14:18:23 +0100
Subject: [PATCH] ndb: add a rpmpkgSalvage() method

This can be used to recover as much data as possibly from a
terminally broken database. It works by scanning the database
file for entries that are not corrupt.

URL:https://github.com/rpm-software-management/rpm/commit/6489957449fec63ddf330330e9435b4ee0c388b0
---
 lib/backend/ndb/rpmpkg.c | 157 +++++++++++++++++++++++++++++++++++++++++++++--
 lib/backend/ndb/rpmpkg.h |   1 +
 2 files changed, 153 insertions(+), 5 deletions(-)

diff --git a/lib/backend/ndb/rpmpkg.c b/lib/backend/ndb/rpmpkg.c
index 922ae11..b9cd67c 100644
--- a/lib/backend/ndb/rpmpkg.c
+++ b/lib/backend/ndb/rpmpkg.c
@@ -30,8 +30,6 @@ typedef struct pkgslot_s {
 
 typedef struct rpmpkgdb_s {
     int fd;			/* our file descriptor */
-    int flags;
-    int mode;
 
     int rdonly;
 
@@ -885,8 +883,6 @@ int rpmpkgOpen(rpmpkgdb *pkgdbp, const char *filename, int flags, int mode)
 	    return RPMRC_FAIL;
 	}
     }
-    pkgdb->flags = flags;
-    pkgdb->mode = mode;
     pkgdb->dofsync = 1;
     *pkgdbp = pkgdb;
     return RPMRC_OK;
@@ -908,6 +904,157 @@ void rpmpkgClose(rpmpkgdb pkgdb)
     free(pkgdb);
 }
 
+
+static unsigned int salvage_latest_blob(unsigned int *salvaged, unsigned int cnt)
+{
+    unsigned int i, max = 0, maxi = 0;
+    for (i = 0; i < cnt - 1; i++) {
+	if (salvaged[i * 4 + 7] - salvaged[i * 4 + 3] > max) {
+	    max = salvaged[i * 4 + 7] - salvaged[i * 4 + 3];
+	    maxi = i;
+	}
+    }
+    if ((unsigned int)(salvaged[3] - salvaged[i * 4 + 3]) > max)
+	maxi = cnt - 1;
+    return maxi;
+}
+
+static int salvage_cmp(const void *av, const void *bv)
+{
+    const unsigned int *a = av, *b = bv;
+    if (a[0] != b[0])
+	return a[0] > b[0] ? 1 : -1;
+    if (a[3] != b[3])
+	return a[3] > b[3] ? 1 : -1;
+    if (a[1] != b[1])
+	return a[1] > b[1] ? 1 : -1;
+    return 0;
+}
+
+#define SALVAGE_PAGESIZE 4096
+#define SALVAGE_BLKCHUNK (SALVAGE_PAGESIZE / BLK_SIZE)
+
+int rpmpkgSalvage(rpmpkgdb *pkgdbp, const char *filename)
+{
+    struct stat stb;
+    rpmpkgdb pkgdb;
+    unsigned int blk, iblk, blkskip;
+    unsigned int blkoff, blkcnt, pkgidx, generation, bloblen;
+    unsigned int nfound, nslots, i, j;
+    unsigned char page[SALVAGE_PAGESIZE];
+    unsigned int *salvaged;
+
+    *pkgdbp = 0;
+    pkgdb = xcalloc(1, sizeof(*pkgdb));
+    pkgdb->filename = xstrdup(filename);
+    pkgdb->rdonly = 1;
+    if ((pkgdb->fd = open(filename, O_RDONLY)) == -1) {
+	rpmpkgClose(pkgdb);
+	return RPMRC_FAIL;
+    }
+    if (rpmpkgGetLock(pkgdb, LOCK_SH)) {
+	rpmpkgClose(pkgdb);
+	return RPMRC_FAIL;
+    }
+    pkgdb->locked_shared++;
+    if (fstat(pkgdb->fd, &stb)) {
+	rpmpkgClose(pkgdb);
+	return RPMRC_FAIL;
+    }
+    if (stb.st_size < BLK_SIZE) {
+	rpmpkgClose(pkgdb);
+	return RPMRC_FAIL;
+    }
+    pkgdb->fileblks = stb.st_size / BLK_SIZE;
+    blkskip = 1;
+    nfound = 0;
+    salvaged = xmalloc(64 * (4 * sizeof(unsigned int)));
+    for (blk = 0; blk < pkgdb->fileblks; blk += SALVAGE_BLKCHUNK) {
+	unsigned int size;
+	unsigned char *bp = page;
+	if (pkgdb->fileblks - blk > SALVAGE_BLKCHUNK)
+	    size = SALVAGE_PAGESIZE;
+	else
+	    size = (pkgdb->fileblks - blk) * BLK_SIZE;
+	if (pread(pkgdb->fd, page, size, (off_t)blk * BLK_SIZE) != size)
+	    continue;
+	if (size != SALVAGE_PAGESIZE)
+	    memset(page + size, 0, SALVAGE_PAGESIZE - size);
+	if (blkskip) {
+	    memset(page, 0, blkskip * BLK_SIZE);
+	    blkskip = 0;
+	}
+	for (iblk = 0; iblk < SALVAGE_BLKCHUNK; iblk++, bp += BLK_SIZE) {
+	    if (le2h(bp) != BLOBHEAD_MAGIC)
+		continue;
+	    pkgidx = le2h(bp + 4);
+	    if (!pkgidx)
+		continue;
+	    generation = le2h(bp + 8);
+	    bloblen = le2h(bp + 12);
+	    blkoff = blk + iblk;
+	    blkcnt = (BLOBHEAD_SIZE + bloblen + BLOBTAIL_SIZE + BLK_SIZE - 1) / BLK_SIZE;
+	    if (blkoff + blkcnt > pkgdb->fileblks)
+		continue;
+	    if (rpmpkgVerifyblob(pkgdb, pkgidx, blkoff, blkcnt))
+		continue;
+	    /* found a valid blob, add to salvaged list */
+	    if (nfound && (nfound & 63) == 0)
+		salvaged = xrealloc(salvaged, (nfound + 64) * (4 * sizeof(unsigned int)));
+	    salvaged[nfound * 4 + 0] = pkgidx;
+	    salvaged[nfound * 4 + 1] = blkoff;
+	    salvaged[nfound * 4 + 2] = blkcnt;
+	    salvaged[nfound * 4 + 3] = generation;
+	    nfound++;
+	    /* advance to after the blob! */
+	    blkoff += blkcnt;
+	    if (blkoff >= blk + SALVAGE_BLKCHUNK) {
+		blkskip = blkoff % SALVAGE_BLKCHUNK;
+		blk = blkoff - blkskip - SALVAGE_BLKCHUNK;
+		break;
+	    } else {
+		iblk = blkoff - blk - 1;
+		bp = page + iblk * BLK_SIZE;
+	    }
+	}
+    }
+    /* now strip duplicate entries */
+    nslots = 0;
+    if (nfound > 1) {
+	qsort(salvaged, nfound, sizeof(unsigned int) * 4, salvage_cmp);
+	for (i = 0; i < nfound; i++) {
+	    pkgidx = salvaged[i * 4];
+	    for (j = i + 1; j < nfound; j++)
+		if (salvaged[j * 4] != pkgidx)
+		    break;
+	    if (j != i + 1)
+		i += salvage_latest_blob(salvaged + i * 4, j - i);
+	    if (i != nslots)
+		memcpy(salvaged + nslots * 4, salvaged + i * 4, sizeof(unsigned int) * 4);
+	    nslots++;
+	    i = j - 1;
+	}
+    }
+    pkgdb->slots = xcalloc(nslots + 1, sizeof(*pkgdb->slots));
+    for (i = 0; i < nslots; i++) {
+	pkgdb->slots[i].pkgidx = salvaged[i * 4 + 0];
+	pkgdb->slots[i].blkoff = salvaged[i * 4 + 1];
+	pkgdb->slots[i].blkcnt = salvaged[i * 4 + 2];
+	pkgdb->slots[i].slotno = 0;
+    }
+    free(salvaged);
+    pkgdb->header_ok = 1;
+    pkgdb->nslots = nslots;
+    pkgdb->ordered = 0;
+    rpmpkgOrderSlots(pkgdb);
+    if (rpmpkgHashSlots(pkgdb)) {
+	rpmpkgClose(pkgdb);
+	return RPMRC_FAIL;
+    }
+    *pkgdbp = pkgdb;
+    return RPMRC_OK;
+}
+
 void rpmpkgSetFsync(rpmpkgdb pkgdb, int dofsync)
 {
     pkgdb->dofsync = dofsync;
@@ -1184,7 +1331,7 @@ int rpmpkgVerify(rpmpkgdb pkgdb)
 
 int rpmpkgNextPkgIdx(rpmpkgdb pkgdb, unsigned int *pkgidxp)
 {
-    if (rpmpkgLockReadHeader(pkgdb, 1))
+    if (rpmpkgLockReadHeader(pkgdb, 1) || !pkgdb->nextpkgidx)
 	return RPMRC_FAIL;
     *pkgidxp = pkgdb->nextpkgidx++;
     if (rpmpkgWriteHeader(pkgdb)) {
diff --git a/lib/backend/ndb/rpmpkg.h b/lib/backend/ndb/rpmpkg.h
index 72d05c4..6466818 100644
--- a/lib/backend/ndb/rpmpkg.h
+++ b/lib/backend/ndb/rpmpkg.h
@@ -2,6 +2,7 @@ struct rpmpkgdb_s;
 typedef struct rpmpkgdb_s *rpmpkgdb;
 
 int rpmpkgOpen(rpmpkgdb *pkgdbp, const char *filename, int flags, int mode);
+int rpmpkgSalvage(rpmpkgdb *pkgdbp, const char *filename);
 void rpmpkgClose(rpmpkgdb pkgdbp);
 void rpmpkgSetFsync(rpmpkgdb pkgdbp, int dofsync);
 
-- 
1.8.3.1

