git-trace

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);
}