romele / git-trace public
62 commits · 1 branch · 1 tag · 1 contributor (top: Romele) · first commit 3d ago · last push 11m ago
src/db.c
#include "db.h" #include "util.h" #include "schema.h" #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> static void db_migrate(sqlite3 *db); sqlite3 *db_open(const char *path) { sqlite3 *db = NULL; if (sqlite3_open(path, &db) != SQLITE_OK) { fprintf(stderr, "git-trace: cannot open database %s: %s\n", path, db ? sqlite3_errmsg(db) : "out of memory"); exit(1); } sqlite3_busy_timeout(db, 5000); char *err = NULL; if (sqlite3_exec(db, SCHEMA_SQL, NULL, NULL, &err) != SQLITE_OK) { fprintf(stderr, "git-trace: schema bootstrap failed: %s\n", err ? err : "unknown error"); sqlite3_free(err); exit(1); } db_migrate(db); return db; } /* In-place upgrades for databases created before a column existed: * CREATE TABLE IF NOT EXISTS leaves old tables untouched, so new * columns are added with guarded ALTER TABLEs. */ static int db_has_column(sqlite3 *db, const char *tbl, const char *col) { char q[96]; snprintf(q, sizeof q, "PRAGMA table_info(%s)", tbl); /* tbl is const */ sqlite3_stmt *st; int found = 0; if (sqlite3_prepare_v2(db, q, -1, &st, NULL) == SQLITE_OK) { while (sqlite3_step(st) == SQLITE_ROW) { const char *n = (const char *)sqlite3_column_text(st, 1); if (n && strcmp(n, col) == 0) { found = 1; break; } } sqlite3_finalize(st); } return found; } static void db_migrate(sqlite3 *db) { static const struct { const char *tbl, *col, *sql; } mig[] = { { "users", "quota_kb", "ALTER TABLE users ADD COLUMN quota_kb" " INTEGER NOT NULL DEFAULT 524288" }, { "repos", "size_kb", "ALTER TABLE repos ADD COLUMN size_kb" " INTEGER NOT NULL DEFAULT 0" }, { "users", "tier", "ALTER TABLE users ADD COLUMN tier" " TEXT NOT NULL DEFAULT 'free'" }, { "users", "tier_expires_at", "ALTER TABLE users ADD COLUMN tier_expires_at INTEGER" }, { "repos", "ci_enabled", "ALTER TABLE repos ADD COLUMN ci_enabled" " INTEGER NOT NULL DEFAULT 0" }, { "repo_mirrors", "direction", "ALTER TABLE repo_mirrors ADD COLUMN direction" " TEXT NOT NULL DEFAULT 'pull'" }, }; for (size_t i = 0; i < sizeof mig / sizeof mig[0]; i++) { if (db_has_column(db, mig[i].tbl, mig[i].col)) continue; if (sqlite3_exec(db, mig[i].sql, NULL, NULL, NULL) != SQLITE_OK) fprintf(stderr, "git-trace: warning: migration %s.%s failed\n", mig[i].tbl, mig[i].col); } } void db_audit(sqlite3 *db, int uid, const char *actor, const char *ip, int repo_id, const char *action, const char *fmt, ...) { char detail[512] = ""; if (fmt) { va_list ap; va_start(ap, fmt); vsnprintf(detail, sizeof detail, fmt, ap); va_end(ap); } sqlite3_stmt *st; if (sqlite3_prepare_v2(db, "INSERT INTO audit_log(uid,actor,ip,action,repo_id,detail)" " VALUES(?,?,?,?,?,?)", -1, &st, NULL) != SQLITE_OK) return; if (uid > 0) sqlite3_bind_int(st, 1, uid); else sqlite3_bind_null(st, 1); if (actor) sqlite3_bind_text(st, 2, actor, -1, SQLITE_TRANSIENT); else sqlite3_bind_null(st, 2); if (ip) sqlite3_bind_text(st, 3, ip, -1, SQLITE_TRANSIENT); else sqlite3_bind_null(st, 3); sqlite3_bind_text(st, 4, action, -1, SQLITE_TRANSIENT); if (repo_id) sqlite3_bind_int(st, 5, repo_id); else sqlite3_bind_null(st, 5); sqlite3_bind_text(st, 6, detail, -1, SQLITE_TRANSIENT); sqlite3_step(st); sqlite3_finalize(st); /* retention: drop entries older than 90 days */ sqlite3_exec(db, "DELETE FROM audit_log WHERE ts < strftime('%s','now') - 7776000", NULL, NULL, NULL); }