From 509cc7d3481324ef04cc3ef82a75fe6715ae064c Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Mon, 20 Jan 2025 23:06:25 +0100 Subject: [PATCH] libstore: make SQLite resource-safe Change-Id: I59133503b4b10e4ee5dc94b8c1da4ecba5262537 --- lix/libstore/sqlite.cc | 22 ++++++++++++---------- lix/libstore/sqlite.hh | 24 ++++++++++++++++-------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/lix/libstore/sqlite.cc b/lix/libstore/sqlite.cc index 0b2a3d159..8b4a77ba4 100644 --- a/lix/libstore/sqlite.cc +++ b/lix/libstore/sqlite.cc @@ -63,11 +63,13 @@ SQLite::SQLite(const Path & path, SQLiteOpenMode mode) int flags = immutable ? SQLITE_OPEN_READONLY : SQLITE_OPEN_READWRITE; if (mode == SQLiteOpenMode::Normal) flags |= SQLITE_OPEN_CREATE; auto uri = "file:" + percentEncode(path) + "?immutable=" + (immutable ? "1" : "0"); + sqlite3 * db; int ret = sqlite3_open_v2(uri.c_str(), &db, SQLITE_OPEN_URI | flags, vfs); if (ret != SQLITE_OK) { const char * err = sqlite3_errstr(ret); throw Error("cannot open SQLite database '%s': %s", path, err); } + this->db.reset(db); if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK) SQLiteError::throw_(db, "setting timeout"); @@ -80,10 +82,10 @@ SQLite::SQLite(const Path & path, SQLiteOpenMode mode) exec("pragma foreign_keys = 1"); } -SQLite::~SQLite() +void SQLite::Close::operator()(sqlite3 * db) { try { - if (db && sqlite3_close(db) != SQLITE_OK) + if (sqlite3_close(db) != SQLITE_OK) SQLiteError::throw_(db, "closing database"); } catch (...) { ignoreExceptionInDestructor(); @@ -99,37 +101,37 @@ void SQLite::isCache() void SQLite::exec(const std::string & stmt) { retrySQLite([&]() { - if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK) - SQLiteError::throw_(db, "executing SQLite statement '%s'", stmt); + if (sqlite3_exec(db.get(), stmt.c_str(), 0, 0, 0) != SQLITE_OK) + SQLiteError::throw_(db.get(), "executing SQLite statement '%s'", stmt); }); } SQLiteStmt SQLite::create(const std::string & stmt) { - return SQLiteStmt(db, stmt); + return SQLiteStmt(db.get(), stmt); } SQLiteTxn SQLite::beginTransaction() { - return SQLiteTxn(db); + return SQLiteTxn(db.get()); } void SQLite::setPersistWAL(bool persist) { int enable = persist ? 1 : 0; - if (sqlite3_file_control(db, nullptr, SQLITE_FCNTL_PERSIST_WAL, &enable) != SQLITE_OK) { - SQLiteError::throw_(db, "setting persistent WAL mode"); + if (sqlite3_file_control(db.get(), nullptr, SQLITE_FCNTL_PERSIST_WAL, &enable) != SQLITE_OK) { + SQLiteError::throw_(db.get(), "setting persistent WAL mode"); } } uint64_t SQLite::getLastInsertedRowId() { - return sqlite3_last_insert_rowid(db); + return sqlite3_last_insert_rowid(db.get()); } uint64_t SQLite::getRowsChanged() { - return sqlite3_changes64(db); + return sqlite3_changes64(db.get()); } SQLiteStmt::SQLiteStmt(sqlite3 * db, const std::string & sql) diff --git a/lix/libstore/sqlite.hh b/lix/libstore/sqlite.hh index 3e7d491ff..e499c3c50 100644 --- a/lix/libstore/sqlite.hh +++ b/lix/libstore/sqlite.hh @@ -32,22 +32,25 @@ enum class SQLiteOpenMode { Immutable, }; +struct SQLiteError; class SQLiteStmt; class SQLiteTxn; /** * RAII wrapper to close a SQLite database automatically. */ -struct SQLite +class SQLite { - sqlite3 * db = 0; - SQLite() { } + friend SQLiteError; + + struct Close { + void operator()(sqlite3 * db); + }; + std::unique_ptr db; + +public: + SQLite() = default; SQLite(const Path & path, SQLiteOpenMode mode = SQLiteOpenMode::Normal); - SQLite(const SQLite & from) = delete; - SQLite& operator = (const SQLite & from) = delete; - SQLite& operator = (SQLite && from) { db = from.db; from.db = 0; return *this; } - ~SQLite(); - operator sqlite3 * () { return db; } /** * Disable synchronous mode, set truncate journal mode. @@ -156,6 +159,11 @@ struct SQLiteError : Error std::string errMsg; int errNo, extendedErrNo, offset; + template + [[noreturn]] static void throw_(SQLite & db, const std::string & fs, const Args & ... args) { + throw_(db.db.get(), HintFmt(fs, args...)); + } + template [[noreturn]] static void throw_(sqlite3 * db, const std::string & fs, const Args & ... args) { throw_(db, HintFmt(fs, args...));