diff --git a/libpkg/pkgdb.c b/libpkg/pkgdb.c index 1720a851c..db4c16a7a 100644 --- a/libpkg/pkgdb.c +++ b/libpkg/pkgdb.c @@ -1049,6 +1049,32 @@ pkgdb_open_all(struct pkgdb **db_p, pkgdb_t type, const char *reponame) vec_free(&r); return (ret); } + +/* + * Probe a freshly opened read-only connection with a trivial schema + * read. For a WAL-mode database this is what triggers the creation or + * opening of the -wal/-shm sidecar files; if they cannot be accessed + * the prepare fails (SQLITE_READONLY or SQLITE_CANTOPEN) even though + * sqlite3_open_v2() itself succeeded. + */ +static bool +pkgdb_ro_probe_ok(sqlite3 *sqlite) +{ + sqlite3_stmt *stmt = NULL; + int rc; + bool ok; + + if (sqlite3_prepare_v2(sqlite, "SELECT 1 FROM sqlite_master LIMIT 1", + -1, &stmt, NULL) != SQLITE_OK) + return (false); + + rc = sqlite3_step(stmt); + /* ROW or DONE means the query ran against the schema. */ + ok = (rc == SQLITE_ROW || rc == SQLITE_DONE); + sqlite3_finalize(stmt); + return (ok); +} + int pkgdb_open_all2(struct pkgdb **db_p, pkgdb_t type, c_charv_t *reponames) { @@ -1108,11 +1134,19 @@ retry: * WAL concurrency (shared memory locking) and avoids * seeing partially-checkpointed pages. * - * Fall back to immutable=1 only when the WAL sidecar - * files (-wal/-shm) are not accessible, which means we - * cannot participate in the WAL protocol at all. - * immutable=1 bypasses WAL/SHM entirely and reads - * directly from the main database file. + * A read-only connection to a WAL-mode database still + * needs the -wal/-shm sidecar files: it creates them on + * first use if they are missing, and opens the -shm to + * take part in the WAL protocol. sqlite3_open_v2() reports + * SQLITE_OK even when this is impossible, so every query + * then fails with SQLITE_READONLY or SQLITE_CANTOPEN. This + * happens in particular for non-root users reading a + * root-owned database whose sidecars were removed after + * the writer closed. + * + * Probe the connection with a schema read and, on failure, + * reopen in immutable mode, which bypasses WAL/SHM entirely + * and reads directly from the main database file. */ if (!create && (type == PKGDB_DEFAULT_READONLY || faccessat(dbdirfd, "local.sqlite", @@ -1129,6 +1163,13 @@ retry: &db->sqlite, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, NULL); + } else if (!pkgdb_ro_probe_ok(db->sqlite)) { + sqlite3_close(db->sqlite); + ret = sqlite3_open_v2( + "file:/local.sqlite?immutable=1", + &db->sqlite, + SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, + NULL); } } else { ret = sqlite3_open("/local.sqlite", &db->sqlite);