/* * nanobbs, a tiny forums software. * Copyright (C) 2025-2026 Xavier Del Campo Romero * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #define _POSIX_C_SOURCE 200809L #include "db.h" #include "defs.h" #include #include #include #include #include #include #include #include static int column(sqlite3 *const db, sqlite3_stmt *const stmt, const char *const name) { const int n = sqlite3_data_count(stmt); if (!n) { fprintf(stderr, "%s: sqlite3_data_count: %s\n", __func__, sqlite3_errmsg(db)); return -1; } for (int i = 0; i < n; i++) { const char *const col = sqlite3_column_name(stmt, i); if (!col) { fprintf(stderr, "%s: sqlite3_column_name: %s\n", __func__, sqlite3_errmsg(db)); return -1; } else if (!strcmp(col, name)) return i; } fprintf(stderr, "%s: could not find column \"%s\"\n", __func__, name); return -1; } int db_int(sqlite3 *const db, sqlite3_stmt *const stmt, const char *const name, int *const out) { const int col = column(db, stmt, name); if (col < 0) return -1; *out = sqlite3_column_int(stmt, col); return 0; } int db_uint(sqlite3 *const db, sqlite3_stmt *const stmt, const char *const name, unsigned *const out) { int v; if (db_int(db, stmt, name, &v)) return -1; else if (v < 0) { fprintf(stderr, "%s: unexpected negative value for %s: %d\n", __func__, name, v); return -1; } *out = v; return 0; } int db_bigint(sqlite3 *const db, sqlite3_stmt *const stmt, const char *const name, long long *const out) { const int col = column(db, stmt, name); if (col < 0) return -1; *out = sqlite3_column_int64(stmt, col); return 0; } int db_biguint(sqlite3 *const db, sqlite3_stmt *const stmt, const char *const name, unsigned long long *const out) { const int col = column(db, stmt, name); sqlite3_int64 v; if (col < 0 || (v = sqlite3_column_int64(stmt, col)) < 0) return -1; *out = v; return 0; } char *db_str(sqlite3 *const db, sqlite3_stmt *const stmt, const char *const name) { char *ret = NULL; const int col = column(db, stmt, name); if (col < 0) return NULL; const unsigned char *const s = sqlite3_column_text(stmt, col); if (!s) { fprintf(stderr, "%s: sqlite3_column_text: %s\n", __func__, sqlite3_errmsg(db)); goto failure; } else if (!(ret = strndup((const char *)s, sqlite3_column_bytes(stmt, col)))) { fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno)); goto failure; } return ret; failure: free(ret); return NULL; } int db_rollback(sqlite3 *const db) { char *msg = NULL; const int e = sqlite3_exec(db, "ROLLBACK;", NULL, NULL, &msg); if (e != SQLITE_OK) fprintf(stderr, "%s: sqlite3_exec: %s, msg=%s\n", __func__, sqlite3_errstr(e), msg); sqlite3_free(msg); return e != SQLITE_OK; } int db_open(const char *const dir, sqlite3 **const out) { int ret = SQLITE_ERROR; sqlite3 *db; struct dynstr d; const mode_t m = umask(S_IWGRP | S_IWOTH | S_IROTH); dynstr_init(&d); if (dynstr_append(&d, "%s/" PROJECT_NAME ".db", dir)) { fprintf(stderr, "%s: dynstr_append failed\n", __func__); goto end; } else if ((ret = sqlite3_open(d.str, &db)) != SQLITE_OK) { fprintf(stderr, "%s: sqlite3_open %s: %s\n", __func__, d.str, sqlite3_errstr(ret)); goto end; } *out = db; ret = 0; end: umask(m); dynstr_free(&d); return ret; }