aboutsummaryrefslogtreecommitdiff
#include "sqlite.hh"
#include "util.hh"

#include <sqlite3.h>

namespace nix {

[[noreturn]] void throwSQLiteError(sqlite3 * db, const format & f)
{
    int err = sqlite3_errcode(db);
    if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
        if (err == SQLITE_PROTOCOL)
            printMsg(lvlError, "warning: SQLite database is busy (SQLITE_PROTOCOL)");
        else {
            static bool warned = false;
            if (!warned) {
                printMsg(lvlError, "warning: SQLite database is busy");
                warned = true;
            }
        }
        /* Sleep for a while since retrying the transaction right away
           is likely to fail again. */
#if HAVE_NANOSLEEP
        struct timespec t;
        t.tv_sec = 0;
        t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */
        nanosleep(&t, 0);
#else
        sleep(1);
#endif
        throw SQLiteBusy(format("%1%: %2%") % f.str() % sqlite3_errmsg(db));
    }
    else
        throw SQLiteError(format("%1%: %2%") % f.str() % sqlite3_errmsg(db));
}

SQLite::~SQLite()
{
    try {
        if (db && sqlite3_close(db) != SQLITE_OK)
            throwSQLiteError(db, "closing database");
    } catch (...) {
        ignoreException();
    }
}

void SQLiteStmt::create(sqlite3 * db, const string & s)
{
    checkInterrupt();
    assert(!stmt);
    if (sqlite3_prepare_v2(db, s.c_str(), -1, &stmt, 0) != SQLITE_OK)
        throwSQLiteError(db, "creating statement");
    this->db = db;
}

SQLiteStmt::~SQLiteStmt()
{
    try {
        if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
            throwSQLiteError(db, "finalizing statement");
    } catch (...) {
        ignoreException();
    }
}

SQLiteStmt::Use::Use(SQLiteStmt & stmt)
    : stmt(stmt)
{
    assert(stmt.stmt);
    /* Note: sqlite3_reset() returns the error code for the most
       recent call to sqlite3_step().  So ignore it. */
    sqlite3_reset(stmt);
}

SQLiteStmt::Use::~Use()
{
    sqlite3_reset(stmt);
}

SQLiteStmt::Use & SQLiteStmt::Use::operator () (const std::string & value, bool notNull)
{
    if (notNull) {
        if (sqlite3_bind_text(stmt, curArg++, value.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
            throwSQLiteError(stmt.db, "binding argument");
    } else
        bind();
    return *this;
}

SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull)
{
    if (notNull) {
        if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK)
            throwSQLiteError(stmt.db, "binding argument");
    } else
        bind();
    return *this;
}

SQLiteStmt::Use & SQLiteStmt::Use::bind()
{
    if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK)
        throwSQLiteError(stmt.db, "binding argument");
    return *this;
}

int SQLiteStmt::Use::step()
{
    return sqlite3_step(stmt);
}

void SQLiteStmt::Use::exec()
{
    int r = step();
    assert(r != SQLITE_ROW);
    if (r != SQLITE_DONE)
        throwSQLiteError(stmt.db, "executing SQLite statement");
}

bool SQLiteStmt::Use::next()
{
    int r = step();
    if (r != SQLITE_DONE && r != SQLITE_ROW)
        throwSQLiteError(stmt.db, "executing SQLite query");
    return r == SQLITE_ROW;
}

std::string SQLiteStmt::Use::getStr(int col)
{
    auto s = (const char *) sqlite3_column_text(stmt, col);
    assert(s);
    return s;
}

int64_t SQLiteStmt::Use::getInt(int col)
{
    // FIXME: detect nulls?
    return sqlite3_column_int64(stmt, col);
}

SQLiteTxn::SQLiteTxn(sqlite3 * db)
{
    this->db = db;
    if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK)
        throwSQLiteError(db, "starting transaction");
    active = true;
}

void SQLiteTxn::commit()
{
    if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK)
        throwSQLiteError(db, "committing transaction");
    active = false;
}

SQLiteTxn::~SQLiteTxn()
{
    try {
        if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK)
            throwSQLiteError(db, "aborting transaction");
    } catch (...) {
        ignoreException();
    }
}

}
-closure '((gnu build marionette) (guix build utils))) #~(begin (use-modules (gnu build marionette) (srfi srfi-26) (srfi srfi-64)) (let ((marionette (make-marionette (list #$vm)))) (test-runner-current (system-test-runner #$output)) (test-begin "xvnc") (test-assert "service running" (marionette-eval '(begin (use-modules (gnu services herd)) (start-service 'xvnc)) marionette)) (test-assert "wait for port 5905, IPv4" (wait-for-tcp-port 5905 marionette)) (test-assert "wait for port 5905, IPv6" (wait-for-tcp-port 5905 marionette #:address '(make-socket-address AF_INET6 (inet-pton AF_INET6 "::1") 5905))) (test-assert "gdm auto-suspend is disabled" ;; More a GDM than a Xvnc test, but since it's a cross-cutting ;; concern and we have everything set up here, we might as well ;; check it here. (marionette-eval '(begin (use-modules (guix build utils)) ;; Check that DCONF_PROFILE is set... (invoke "/bin/sh" "-lc" "\ pgrep gdm | head -n1 | xargs -I{} grep -Fq DCONF_PROFILE /proc/{}/environ") ;; ... and that 'sleep-inactive-ac-type' is unset. (invoke "/bin/sh" "-lc" "\ sudo -E -u gdm env DCONF_PROFILE=/etc/dconf/profile/gdm dbus-run-session \ gsettings get org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type \ | grep -Fq nothing")) marionette)) (test-group "vnc lands on the gdm login screen" ;; This test runs vncviewer on the local VM and verifies that it ;; manages to access the GDM login screen (via XDMCP). (define (ratpoison-abort) (marionette-control "sendkey ctrl-g" marionette)) (define (ratpoison-help) (marionette-control "sendkey ctrl-t" marionette) (marionette-type "?" marionette) (sleep 1)) ;wait for help screen to appear (define (ratpoison-exec command) (marionette-control "sendkey ctrl-t" marionette) (marionette-type "!" marionette) (marionette-type (string-append command "\n") marionette)) ;; Wait until the ratpoison help screen can be displayed; this ;; means the window manager is ready. ;; XXX: The letters are half of the height preferred by ;; GNU Ocrad, scale it by 2. (test-assert "window manager is ready" (wait-for-screen-text marionette (cut string-contains <> "key bindings") #:ocr #$ocr #:ocr-arguments '("--scale=2") #:pre-action ratpoison-help #:post-action ratpoison-abort)) ;; Run vncviewer and expect the GDM login screen (accessed via ;; XDMCP). This can take a while to appear on slower machines. (ratpoison-exec "vncviewer localhost:5905") (test-assert "GDM login screen ready" ;; XXX: The '--invert' argument as the sole option to GNU ;; Ocrad is required for it to recognize "Guix" from the ;; background image. 'Username' from the UI would be a better ;; choice but is not recognized at all. (wait-for-screen-text marionette (cut string-contains <> "Guix") #:ocr #$ocr #:ocr-arguments '("--invert") #:timeout 120))) ;for slow systems (test-end))))) (gexp->derivation "xvnc-test" test)) (define %test-xvnc (system-test (name "xvnc") (description "Basic tests for the Xvnc service. One of the tests validate that XDMCP works with GDM, and is therefore heavy in terms of disk and memory requirements.") (value (run-xvnc-test))))