From 6bede8f34d0b0f52562a09c2385a4f112fd16eb2 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Wed, 29 Jul 2026 07:15:02 -0400 Subject: [PATCH] fix(upgrade): __db_set_lastpgno stored page count, not last page number __db_lastpgno() returns the page COUNT (file bytes / pagesize) -- its other caller __db_page_pass uses it correctly as a loop bound (for i=0; iv9 upgrade path (__db_upgrade case 8, a pure metadata pass). db_verify then rejects the upgraded file under HAVE_FTRUNCATE with "Page 0: last_pgno is not correct: N != N-1". The bug is byte-identical to upstream Berkeley DB 4.7/4.8, i.e. a long-standing latent defect; the hash upgrade path happened to mask it (its v8->v9 pass goes through mpool, which recomputes last_pgno on close). Fix: store the last page NUMBER (count - 1), mapping an impossible 0-page file to PGNO_INVALID rather than underflowing. Found by the access-method upgrade coverage work (PR #75, which exercises the per-version transforms via synthetic old-format fixtures). Verified: a v8 meta upgraded with db_upgrade now passes db_verify (was "last_pgno is not correct"); normal v9 upgrade + btree/hash test001 unaffected. --- src/db/db_upg.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/db/db_upg.c b/src/db/db_upg.c index 6e9b91abd..6f6069636 100644 --- a/src/db/db_upg.c +++ b/src/db/db_upg.c @@ -506,6 +506,7 @@ __db_set_lastpgno(dbp, real_name, fhp) { DBMETA meta; ENV *env; + db_pgno_t last_pgno; int ret; size_t n; @@ -515,8 +516,18 @@ __db_set_lastpgno(dbp, real_name, fhp) if ((ret = __os_read(env, fhp, &meta, sizeof(meta), &n)) != 0) return (ret); dbp->pgsize = meta.pagesize; - if ((ret = __db_lastpgno(dbp, real_name, fhp, &meta.last_pgno)) != 0) + /* + * __db_lastpgno returns the page COUNT (file bytes / pagesize), but + * meta.last_pgno is the last valid page NUMBER (0-indexed). For an + * N-page file the last page number is N-1, so convert; a page-count of + * 0 (impossible for a real DB, which always has a meta page) would map + * to PGNO_INVALID rather than underflowing. Storing the raw count here + * left last_pgno one too high, which db_verify rejects under + * HAVE_FTRUNCATE ("last_pgno is not correct"). + */ + if ((ret = __db_lastpgno(dbp, real_name, fhp, &last_pgno)) != 0) return (ret); + meta.last_pgno = last_pgno == 0 ? PGNO_INVALID : last_pgno - 1; if ((ret = __os_seek(env, fhp, 0, 0, 0)) != 0) return (ret); if ((ret = __os_write(env, fhp, &meta, sizeof(meta), &n)) != 0)