1
0
mirror of git://git.gnupg.org/gnupg.git synced 2025-04-17 15:44:34 +02:00

gpg: Cache prepared SQL queries and open DB connections.

* g10/tofu.c: Include <stdarg.h>.
(prepares_saved) [DEBUG_TOFU_CACHE]: New variable.
(queries) [DEBUG_TOFU_CACHE]: New variable.
(struct db): Add fields prevp, begin_transaction, end_transaction,
rollback, record_binding_get_old_policy, record_binding_update,
record_binding_update2, get_policy_select_policy_and_conflict,
get_trust_bindings_with_this_email, get_trust_gather_other_user_ids,
get_trust_gather_other_keys, register_already_seen, and
register_insert.
[DEBUG_TOFU_CACHE]: Add field hits.
(STRINGIFY): New macro.
(STRINGIFY2): New macro.
(enum sqlite_arg_type): New enum.
(sqlite3_stepx): New function.
(combined_db): Remove variable.
(opendb): Don't cache the combined db.
(struct dbs): New struct.  Update users to use this as the head of the
local DB list rather than overloading struct db.
(unlink_db): New function.
(link_db): New function.
(db_cache): New variable.
(db_cache_count): New variable.
(DB_CACHE_ENTRIES): Define.
(getdb): If the dbs specific cache doesn't include the DB, look at
DB_CACHE.  Only if that also doesn't include the DB open the
corresponding DB.
(closedb): New function.
(opendbs): Don't open the combined DB.  Just return an initialized
struct dbs.
(closedbs): Don't close the dbs specific dbs.  Attach them to the
front of DB_CACHE.  If DB_CACHE contains more than DB_CACHE_ENTRIES,
close enough dbs from the end of the DB_CACHE list such that DB_CACHE
only contains DB_CACHE_ENTRIES.  Don't directly close the dbs, instead
use the new closedb function.
[DEBUG_TOFU_CACHE]: Print out some statistics.
(record_binding): Use sqlite3_stepx instead of sqlite3_exec or
sqlite3_exec_printf.
(get_policy): Likewise.
(get_trust): Likewise.
(tofu_register): Likewise.

--
Signed-off-by: Neal H. Walfield <neal@g10code.com>
This commit is contained in:
Neal H. Walfield 2015-10-23 13:42:50 +02:00
parent cd879d4bd6
commit 297cf8660c

View File

@ -27,6 +27,7 @@
#include <stdio.h> #include <stdio.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <assert.h> #include <assert.h>
#include <stdarg.h>
#include <sqlite3.h> #include <sqlite3.h>
#include "gpg.h" #include "gpg.h"
@ -41,6 +42,12 @@
#include "tofu.h" #include "tofu.h"
#define DEBUG_TOFU_CACHE 0
#if DEBUG_TOFU_CACHE
static int prepares_saved;
static int queries;
#endif
/* The TOFU data can be saved in two different formats: either in a /* The TOFU data can be saved in two different formats: either in a
single combined database (opt.tofu_db_format == TOFU_DB_FLAT) or in single combined database (opt.tofu_db_format == TOFU_DB_FLAT) or in
a split file format (opt.tofu_db_format == TOFU_DB_SPLIT). In the a split file format (opt.tofu_db_format == TOFU_DB_SPLIT). In the
@ -71,17 +78,43 @@ enum db_type
struct db struct db
{ {
struct db *next; struct db *next;
struct db **prevp;
enum db_type type; enum db_type type;
sqlite3 *db; sqlite3 *db;
struct
{
sqlite3_stmt *begin_transaction;
sqlite3_stmt *end_transaction;
sqlite3_stmt *rollback;
sqlite3_stmt *record_binding_get_old_policy;
sqlite3_stmt *record_binding_update;
sqlite3_stmt *record_binding_update2;
sqlite3_stmt *get_policy_select_policy_and_conflict;
sqlite3_stmt *get_trust_bindings_with_this_email;
sqlite3_stmt *get_trust_gather_other_user_ids;
sqlite3_stmt *get_trust_gather_other_keys;
sqlite3_stmt *register_already_seen;
sqlite3_stmt *register_insert;
} s;
#if DEBUG_TOFU_CACHE
int hits;
#endif
/* If TYPE is DB_COMBINED, this is "". Otherwise, it is either the /* If TYPE is DB_COMBINED, this is "". Otherwise, it is either the
fingerprint (type == DB_KEY) or the normalized email address fingerprint (type == DB_KEY) or the normalized email address
(type == DB_EMAIL). */ (type == DB_EMAIL). */
char name[1]; char name[1];
}; };
#define STRINGIFY(s) STRINGIFY2(s)
#define STRINGIFY2(s) #s
/* The grouping parameters when collecting signature statistics. */ /* The grouping parameters when collecting signature statistics. */
/* If a message is signed a couple of hours in the future, just assume /* If a message is signed a couple of hours in the future, just assume
@ -187,6 +220,186 @@ sqlite3_exec_printf (sqlite3 *db,
return rc; return rc;
} }
enum sqlite_arg_type
{
SQLITE_ARG_END = 0xdead001,
SQLITE_ARG_INT,
SQLITE_ARG_LONG_LONG,
SQLITE_ARG_STRING
};
static int
sqlite3_stepx (sqlite3 *db,
sqlite3_stmt **stmtp,
int (*callback) (void*,int,char**,char**),
void *cookie,
char **errmsg,
const char *sql, ...)
{
int rc;
int err = 0;
sqlite3_stmt *stmt = NULL;
va_list va;
int args;
enum sqlite_arg_type t;
int i;
int cols;
/* Names of the columns. We initialize this lazily to avoid the
overhead in case the query doesn't return any results. */
const char **azColName = 0;
int callback_initialized = 0;
const char **azVals = 0;
callback_initialized = 0;
if (stmtp && *stmtp)
{
stmt = *stmtp;
/* Make sure this statement is associated with the supplied db. */
assert (db == sqlite3_db_handle (stmt));
#if DEBUG_TOFU_CACHE
prepares_saved ++;
#endif
}
else
{
rc = sqlite3_prepare_v2 (db, sql, -1, &stmt, NULL);
if (rc)
log_fatal ("failed to prepare SQL: %s", sql);
if (stmtp)
*stmtp = stmt;
}
#if DEBUG_TOFU_CACHE
queries ++;
#endif
args = sqlite3_bind_parameter_count (stmt);
va_start (va, sql);
if (args)
{
for (i = 1; i <= args; i ++)
{
t = va_arg (va, enum sqlite_arg_type);
switch (t)
{
case SQLITE_ARG_INT:
{
int value = va_arg (va, int);
err = sqlite3_bind_int (stmt, i, value);
break;
}
case SQLITE_ARG_LONG_LONG:
{
long long value = va_arg (va, long long);
err = sqlite3_bind_int64 (stmt, i, value);
break;
}
case SQLITE_ARG_STRING:
{
char *text = va_arg (va, char *);
err = sqlite3_bind_text (stmt, i, text, -1, SQLITE_STATIC);
break;
}
default:
/* Internal error. Likely corruption. */
log_fatal ("Bad value for parameter type %d.\n", t);
}
if (err)
{
log_fatal ("Error binding parameter %d\n", i);
goto out;
}
}
}
t = va_arg (va, enum sqlite_arg_type);
assert (t == SQLITE_ARG_END);
va_end (va);
for (;;)
{
rc = sqlite3_step (stmt);
if (rc != SQLITE_ROW)
/* No more data (SQLITE_DONE) or an error occured. */
break;
if (! callback)
continue;
if (! callback_initialized)
{
cols = sqlite3_column_count (stmt);
azColName = xmalloc (2 * cols * sizeof (const char *) + 1);
for (i = 0; i < cols; i ++)
azColName[i] = sqlite3_column_name (stmt, i);
callback_initialized = 1;
}
azVals = &azColName[cols];
for (i = 0; i < cols; i ++)
{
azVals[i] = sqlite3_column_text (stmt, i);
if (! azVals[i] && sqlite3_column_type (stmt, i) != SQLITE_NULL)
/* Out of memory. */
{
err = SQLITE_NOMEM;
break;
}
}
if (callback (cookie, cols, (char **) azVals, (char **) azColName))
/* A non-zero result means to abort. */
{
err = SQLITE_ABORT;
break;
}
}
out:
xfree (azColName);
if (stmtp)
rc = sqlite3_reset (stmt);
else
rc = sqlite3_finalize (stmt);
if (rc == SQLITE_OK && err)
/* Local error. */
{
rc = err;
if (errmsg)
{
const char *e = sqlite3_errstr (err);
size_t l = strlen (e) + 1;
*errmsg = sqlite3_malloc (l);
if (! *errmsg)
log_fatal ("Out of memory.\n");
memcpy (*errmsg, e, l);
}
}
else if (rc != SQLITE_OK && errmsg)
/* Error reported by sqlite. */
{
const char * e = sqlite3_errmsg (db);
size_t l = strlen (e) + 1;
*errmsg = sqlite3_malloc (l);
if (! *errmsg)
log_fatal ("Out of memory.\n");
memcpy (*errmsg, e, l);
}
return rc;
}
/* Collect results of a select count (*) ...; style query. Aborts if /* Collect results of a select count (*) ...; style query. Aborts if
the argument is not a valid integer (or real of the form X.0). */ the argument is not a valid integer (or real of the form X.0). */
@ -451,8 +664,6 @@ initdb (sqlite3 *db, enum db_type type)
} }
} }
static sqlite3 *combined_db;
/* Open and initialize a low-level TOFU database. Returns NULL on /* Open and initialize a low-level TOFU database. Returns NULL on
failure. This function should not normally be directly called to failure. This function should not normally be directly called to
get a database handle. Instead, use getdb(). */ get a database handle. Instead, use getdb(). */
@ -468,9 +679,6 @@ opendb (char *filename, enum db_type type)
assert (! filename); assert (! filename);
assert (type == DB_COMBINED); assert (type == DB_COMBINED);
if (combined_db)
return combined_db;
filename = make_filename (opt.homedir, "tofu.db", NULL); filename = make_filename (opt.homedir, "tofu.db", NULL);
filename_free = 1; filename_free = 1;
} }
@ -502,12 +710,36 @@ opendb (char *filename, enum db_type type)
db = NULL; db = NULL;
} }
if (opt.tofu_db_format == TOFU_DB_FLAT)
combined_db = db;
return db; return db;
} }
struct dbs
{
struct db *db;
};
static void
unlink_db (struct db *db)
{
*db->prevp = db->next;
if (db->next)
db->next->prevp = db->prevp;
}
static void
link_db (struct db **head, struct db *db)
{
db->next = *head;
if (db->next)
db->next->prevp = &db->next;
db->prevp = head;
*head = db;
}
static struct db *db_cache;
static int db_cache_count;
#define DB_CACHE_ENTRIES 16
/* Return a database handle. <type, name> describes the required /* Return a database handle. <type, name> describes the required
database. If there is a cached handle in DBS, that handle is database. If there is a cached handle in DBS, that handle is
returned. Otherwise, the database is opened and cached in DBS. returned. Otherwise, the database is opened and cached in DBS.
@ -517,38 +749,38 @@ opendb (char *filename, enum db_type type)
TYPE must be either DB_MAIL or DB_KEY. In the combined format, the TYPE must be either DB_MAIL or DB_KEY. In the combined format, the
combined DB is always returned. */ combined DB is always returned. */
static struct db * static struct db *
getdb (struct db *dbs, const char *name, enum db_type type) getdb (struct dbs *dbs, const char *name, enum db_type type)
{ {
struct db *t = NULL; struct db *t = NULL;
sqlite3 *sqlitedb = NULL;
char *name_sanitized = NULL; char *name_sanitized = NULL;
int count;
char *filename = NULL; char *filename = NULL;
int i; int need_link = 1;
sqlite3 *sqlitedb = NULL;
assert (dbs);
assert (name); assert (name);
assert (type == DB_EMAIL || type == DB_KEY); assert (type == DB_EMAIL || type == DB_KEY);
assert (dbs);
/* The first entry is always for the combined DB. */
assert (dbs->type == DB_COMBINED);
assert (! dbs->name[0]);
if (opt.tofu_db_format == TOFU_DB_FLAT) if (opt.tofu_db_format == TOFU_DB_FLAT)
/* When using the flat format, we only have a single combined /* When using the flat format, we only have a single DB, the
DB. */ combined DB. */
{ {
assert (dbs->db); if (dbs->db)
assert (! dbs->next); {
return dbs; assert (dbs->db->type == DB_COMBINED);
assert (! dbs->db->next);
return dbs->db;
} }
else
/* When using the split format the first entry on the DB list is a
dummy entry. */
assert (! dbs->db);
/* We have the split format. */ type = DB_COMBINED;
}
if (type != DB_COMBINED)
/* Only allow alpha-numeric characters in the name. */
{
int i;
/* Only allow alpha-numeric characters in the filename. */
name_sanitized = xstrdup (name); name_sanitized = xstrdup (name);
for (i = 0; name[i]; i ++) for (i = 0; name[i]; i ++)
{ {
@ -558,12 +790,32 @@ getdb (struct db *dbs, const char *name, enum db_type type)
|| ('0' <= c && c <= '9'))) || ('0' <= c && c <= '9')))
name_sanitized[i] = '_'; name_sanitized[i] = '_';
} }
}
/* See if the DB is cached. */ /* See if the DB is cached. */
for (t = dbs->next; t; t = t->next) for (t = dbs->db; t; t = t->next)
if (type == t->type && strcmp (t->name, name_sanitized) == 0) if (t->type == type
&& (type == DB_COMBINED || strcmp (t->name, name_sanitized) == 0))
{
need_link = 0;
goto out; goto out;
}
for (t = db_cache, count = 0; t; t = t->next, count ++)
if (type == t->type
&& (type == DB_COMBINED || strcmp (t->name, name_sanitized) == 0))
{
unlink_db (t);
db_cache_count --;
goto out;
}
assert (db_cache_count == count);
if (type == DB_COMBINED)
filename = NULL;
else
{
/* Open the DB. The filename has the form: /* Open the DB. The filename has the form:
tofu.d/TYPE/PREFIX/NAME.db tofu.d/TYPE/PREFIX/NAME.db
@ -588,37 +840,74 @@ getdb (struct db *dbs, const char *name, enum db_type type)
(opt.homedir, "tofu.d", type_str, prefix, name_db, NULL); (opt.homedir, "tofu.d", type_str, prefix, name_db, NULL);
xfree (name_db); xfree (name_db);
} }
}
sqlitedb = opendb (filename, type); sqlitedb = opendb (filename, type);
if (! sqlitedb) if (! sqlitedb)
goto out; goto out;
t = xmalloc (sizeof (struct db) + strlen (name_sanitized)); t = xmalloc_clear (sizeof (struct db)
+ (name_sanitized ? strlen (name_sanitized) : 0));
t->type = type; t->type = type;
t->db = sqlitedb; t->db = sqlitedb;
if (name_sanitized)
strcpy (t->name, name_sanitized); strcpy (t->name, name_sanitized);
/* Insert it immediately after the first element. */
t->next = dbs->next;
dbs->next = t;
out: out:
if (t && need_link)
link_db (&dbs->db, t);
#if DEBUG_TOFU_CACHE
if (t)
t->hits ++;
#endif
xfree (filename); xfree (filename);
xfree (name_sanitized); xfree (name_sanitized);
if (! t)
return NULL;
return t; return t;
} }
static void
closedb (struct db *db)
{
sqlite3_stmt **statements;
if (opt.tofu_db_format == TOFU_DB_FLAT)
/* If we are using the flat format, then there is only ever the
combined DB. */
assert (! db->next);
if (db->type == DB_COMBINED)
{
assert (opt.tofu_db_format == TOFU_DB_FLAT);
assert (! db->name[0]);
}
else
{
assert (opt.tofu_db_format == TOFU_DB_SPLIT);
assert (db->type != DB_COMBINED);
assert (db->name[0]);
}
for (statements = &db->s.begin_transaction;
(void *) statements < (void *) &(&db->s)[1];
statements ++)
sqlite3_finalize (*statements);
sqlite3_close (db->db);
#if DEBUG_TOFU_CACHE
log_debug ("Freeing db. Used %d times.\n", db->hits);
#endif
xfree (db);
}
/* Create a new DB meta-handle. Returns NULL on error. */ /* Create a new DB meta-handle. Returns NULL on error. */
static struct db * static struct dbs *
opendbs (void) opendbs (void)
{ {
sqlite3 *db = NULL;
struct db *dbs;
if (opt.tofu_db_format == TOFU_DB_AUTO) if (opt.tofu_db_format == TOFU_DB_AUTO)
{ {
char *filename = make_filename (opt.homedir, "tofu.db", NULL); char *filename = make_filename (opt.homedir, "tofu.db", NULL);
@ -679,88 +968,63 @@ opendbs (void)
} }
} }
if (opt.tofu_db_format == TOFU_DB_FLAT) return xmalloc_clear (sizeof (struct dbs));
{
db = opendb (NULL, DB_COMBINED);
if (! db)
return NULL;
}
else
{
/* Create a dummy entry so that we have a handle. */
}
dbs = xmalloc_clear (sizeof (*dbs));
dbs->db = db;
dbs->type = DB_COMBINED;
return dbs;
} }
/* Release all of the resources associated with a DB meta-handle. */ /* Release all of the resources associated with a DB meta-handle. */
static void static void
closedbs (struct db *dbs) closedbs (struct dbs *dbs)
{ {
if (dbs->db)
{
struct db *old_head = db_cache;
struct db *db; struct db *db;
struct db *n; int count;
/* The first entry is always the combined DB. */ /* Find the last DB. */
assert (dbs->type == DB_COMBINED); for (db = dbs->db, count = 1; db->next; db = db->next, count ++)
if (opt.tofu_db_format == TOFU_DB_FLAT) ;
{
/* If we are using the flat format, then there is only ever the
combined DB. */
assert (! dbs->next);
assert (dbs->db);
assert (dbs->db == combined_db);
}
else
/* In the split format, the combined record is just a place holder
so that we have a stable handle. */
assert (! dbs->db);
for (db = dbs; db; db = n) /* Join the two lists. */
{ db->next = db_cache;
n = db->next; if (db_cache)
db_cache->prevp = &db->next;
if (combined_db && db->db == combined_db) /* Update the (new) first element. */
db_cache = dbs->db;
dbs->db->prevp = &db_cache;
db_cache_count += count;
/* Make sure that we don't have too many DBs on DB_CACHE. If
so, free some. */
if (db_cache_count > DB_CACHE_ENTRIES)
{ {
assert (opt.tofu_db_format == TOFU_DB_FLAT); /* We need to find the (DB_CACHE_ENTRIES + 1)th entry. It
assert (dbs == db); is easy to skip the first COUNT entries since we still
assert (db->type == DB_COMBINED); have a handle on the old head. */
assert (! db->name[0]); int skip = DB_CACHE_ENTRIES - count;
while (-- skip > 0)
old_head = old_head->next;
*old_head->prevp = NULL;
while (old_head)
{
db = old_head->next;
closedb (old_head);
old_head = db;
db_cache_count --;
} }
else if (db->db)
/* Not the dummy entry. */
{
if (dbs == db)
/* The first entry. */
{
assert (opt.tofu_db_format == TOFU_DB_FLAT);
assert (db->type == DB_COMBINED);
assert (! db->name[0]);
} }
else
/* Not the first entry. */
{
assert (opt.tofu_db_format == TOFU_DB_SPLIT);
assert (db->type != DB_COMBINED);
assert (db->name[0]);
} }
sqlite3_close (db->db); xfree (dbs);
}
else
/* The dummy entry. */
{
assert (opt.tofu_db_format == TOFU_DB_SPLIT);
assert (dbs == db);
assert (db->type == DB_COMBINED);
assert (! db->name[0]);
}
xfree (db); #if DEBUG_TOFU_CACHE
} log_debug ("Queries: %d (prepares saved: %d)\n",
queries, prepares_saved);
#endif
} }
@ -790,7 +1054,7 @@ get_single_long_cb (void *cookie, int argc, char **argv, char **azColName)
If SHOW_OLD is set, the binding's old policy is displayed. */ If SHOW_OLD is set, the binding's old policy is displayed. */
static gpg_error_t static gpg_error_t
record_binding (struct db *dbs, const char *fingerprint, const char *email, record_binding (struct dbs *dbs, const char *fingerprint, const char *email,
const char *user_id, enum tofu_policy policy, int show_old) const char *user_id, enum tofu_policy policy, int show_old)
{ {
struct db *db_email = NULL, *db_key = NULL; struct db *db_email = NULL, *db_key = NULL;
@ -820,7 +1084,9 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
if (! db_key) if (! db_key)
return gpg_error (GPG_ERR_GENERAL); return gpg_error (GPG_ERR_GENERAL);
rc = sqlite3_exec (db_email->db, "begin transaction;", NULL, NULL, &err); rc = sqlite3_stepx (db_email->db, &db_email->s.begin_transaction,
NULL, NULL, &err,
"begin transaction;", SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_error (_("error beginning transaction on TOFU %s database: %s\n"), log_error (_("error beginning transaction on TOFU %s database: %s\n"),
@ -829,7 +1095,9 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
return gpg_error (GPG_ERR_GENERAL); return gpg_error (GPG_ERR_GENERAL);
} }
rc = sqlite3_exec (db_key->db, "begin transaction;", NULL, NULL, &err); rc = sqlite3_stepx (db_key->db, &db_key->s.begin_transaction,
NULL, NULL, &err,
"begin transaction;", SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_error (_("error beginning transaction on TOFU %s database: %s\n"), log_error (_("error beginning transaction on TOFU %s database: %s\n"),
@ -844,10 +1112,12 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
purposes, there is no need to start a transaction or to die if purposes, there is no need to start a transaction or to die if
there is a failure. */ there is a failure. */
{ {
rc = sqlite3_exec_printf rc = sqlite3_stepx
(db_email->db, get_single_long_cb, &policy_old, &err, (db_email->db, &db_email->s.record_binding_get_old_policy,
"select policy from bindings where fingerprint = %Q and email = %Q", get_single_long_cb, &policy_old, &err,
fingerprint, email); "select policy from bindings where fingerprint = ? and email = ?",
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_debug ("TOFU: Error reading from binding database" log_debug ("TOFU: Error reading from binding database"
@ -875,17 +1145,20 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
/* Nothing to do. */ /* Nothing to do. */
goto out; goto out;
rc = sqlite3_exec_printf rc = sqlite3_stepx
(db_email->db, NULL, NULL, &err, (db_email->db, &db_email->s.record_binding_update, NULL, NULL, &err,
"insert or replace into bindings\n" "insert or replace into bindings\n"
" (oid, fingerprint, email, user_id, time, policy)\n" " (oid, fingerprint, email, user_id, time, policy)\n"
" values (\n" " values (\n"
/* If we don't explicitly reuse the OID, then SQLite will /* If we don't explicitly reuse the OID, then SQLite will
reallocate a new one. We just need to search for the OID reallocate a new one. We just need to search for the OID
based on the fingerprint and email since they are unique. */ based on the fingerprint and email since they are unique. */
" (select oid from bindings where fingerprint = %Q and email = %Q),\n" " (select oid from bindings where fingerprint = ? and email = ?),\n"
" %Q, %Q, %Q, strftime('%%s','now'), %d);", " ?, ?, ?, strftime('%s','now'), ?);",
fingerprint, email, fingerprint, email, user_id, policy); SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_STRING, user_id, SQLITE_ARG_INT, (int) policy,
SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_error (_("error updating TOFU binding database" log_error (_("error updating TOFU binding database"
@ -901,17 +1174,19 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
{ {
assert (opt.tofu_db_format == TOFU_DB_SPLIT); assert (opt.tofu_db_format == TOFU_DB_SPLIT);
rc = sqlite3_exec_printf rc = sqlite3_stepx
(db_key->db, NULL, NULL, &err, (db_key->db, &db_key->s.record_binding_update2, NULL, NULL, &err,
"insert or replace into bindings\n" "insert or replace into bindings\n"
" (oid, fingerprint, email, user_id)\n" " (oid, fingerprint, email, user_id)\n"
" values (\n" " values (\n"
/* If we don't explicitly reuse the OID, then SQLite will /* If we don't explicitly reuse the OID, then SQLite will
reallocate a new one. We just need to search for the OID reallocate a new one. We just need to search for the OID
based on the fingerprint and email since they are unique. */ based on the fingerprint and email since they are unique. */
" (select oid from bindings where fingerprint = %Q and email = %Q),\n" " (select oid from bindings where fingerprint = ? and email = ?),\n"
" %Q, %Q, %Q);", " ?, ?, ?);",
fingerprint, email, fingerprint, email, user_id); SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_STRING, user_id, SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_error (_("error updating TOFU binding database" log_error (_("error updating TOFU binding database"
@ -930,8 +1205,14 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
{ {
int rc2; int rc2;
rc2 = sqlite3_exec_printf (db_key->db, NULL, NULL, &err, if (rc)
rc ? "rollback;" : "end transaction;"); rc2 = sqlite3_stepx (db_key->db, &db_key->s.rollback,
NULL, NULL, &err,
"rollback;", SQLITE_ARG_END);
else
rc2 = sqlite3_stepx (db_key->db, &db_key->s.end_transaction,
NULL, NULL, &err,
"end transaction;", SQLITE_ARG_END);
if (rc2) if (rc2)
{ {
log_error (_("error ending transaction on TOFU database: %s\n"), log_error (_("error ending transaction on TOFU database: %s\n"),
@ -940,8 +1221,14 @@ record_binding (struct db *dbs, const char *fingerprint, const char *email,
} }
out_revert_one: out_revert_one:
rc2 = sqlite3_exec_printf (db_email->db, NULL, NULL, &err, if (rc)
rc ? "rollback;" : "end transaction;"); rc2 = sqlite3_stepx (db_email->db, &db_email->s.rollback,
NULL, NULL, &err,
"rollback;", SQLITE_ARG_END);
else
rc2 = sqlite3_stepx (db_email->db, &db_email->s.end_transaction,
NULL, NULL, &err,
"end transaction;", SQLITE_ARG_END);
if (rc2) if (rc2)
{ {
log_error (_("error ending transaction on TOFU database: %s\n"), log_error (_("error ending transaction on TOFU database: %s\n"),
@ -1154,7 +1441,7 @@ time_ago_unit (signed long t)
if CONFLICT is not NULL. Returns _tofu_GET_POLICY_ERROR if an error if CONFLICT is not NULL. Returns _tofu_GET_POLICY_ERROR if an error
occurs. */ occurs. */
static enum tofu_policy static enum tofu_policy
get_policy (struct db *dbs, const char *fingerprint, const char *email, get_policy (struct dbs *dbs, const char *fingerprint, const char *email,
char **conflict) char **conflict)
{ {
struct db *db; struct db *db;
@ -1172,11 +1459,13 @@ get_policy (struct db *dbs, const char *fingerprint, const char *email,
(TOFU_POLICY_NONE cannot appear in the DB. Thus, if POLICY is (TOFU_POLICY_NONE cannot appear in the DB. Thus, if POLICY is
still TOFU_POLICY_NONE after executing the query, then the still TOFU_POLICY_NONE after executing the query, then the
result set was empty.) */ result set was empty.) */
rc = sqlite3_exec_printf rc = sqlite3_stepx (db->db, &db->s.get_policy_select_policy_and_conflict,
(db->db, strings_collect_cb, &strlist, &err, strings_collect_cb, &strlist, &err,
"select policy, conflict from bindings\n" "select policy, conflict from bindings\n"
" where fingerprint = %Q and email = %Q", " where fingerprint = ? and email = ?",
fingerprint, email); SQLITE_ARG_STRING, fingerprint,
SQLITE_ARG_STRING, email,
SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_error (_("error reading from TOFU database" log_error (_("error reading from TOFU database"
@ -1264,7 +1553,7 @@ get_policy (struct db *dbs, const char *fingerprint, const char *email,
conflicting binding's policy to TOFU_POLICY_ASK. In either case, conflicting binding's policy to TOFU_POLICY_ASK. In either case,
we return TRUST_UNDEFINED. */ we return TRUST_UNDEFINED. */
static enum tofu_policy static enum tofu_policy
get_trust (struct db *dbs, const char *fingerprint, const char *email, get_trust (struct dbs *dbs, const char *fingerprint, const char *email,
const char *user_id, int may_ask) const char *user_id, int may_ask)
{ {
struct db *db; struct db *db;
@ -1408,16 +1697,17 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
(need to check for a conflict). (need to check for a conflict).
*/ */
/* Look for conflicts. This is need in all 3 cases. /* Look for conflicts. This is needed in all 3 cases.
Get the fingerprints of any bindings that share the email Get the fingerprints of any bindings that share the email
address. Note: if the binding in question is in the DB, it will address. Note: if the binding in question is in the DB, it will
also be returned. Thus, if the result set is empty, then this is also be returned. Thus, if the result set is empty, then this is
a new binding. */ a new binding. */
rc = sqlite3_exec_printf rc = sqlite3_stepx
(db->db, strings_collect_cb, &bindings_with_this_email, &err, (db->db, &db->s.get_trust_bindings_with_this_email,
"select distinct fingerprint from bindings where email = %Q;", strings_collect_cb, &bindings_with_this_email, &err,
email); "select distinct fingerprint from bindings where email = ?;",
SQLITE_ARG_STRING, email, SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_error (_("error reading from TOFU database" log_error (_("error reading from TOFU database"
@ -1556,11 +1846,13 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
if (db_key) if (db_key)
{ {
rc = sqlite3_exec_printf rc = sqlite3_stepx
(db_key->db, strings_collect_cb, &other_user_ids, &err, (db_key->db, &db_key->s.get_trust_gather_other_user_ids,
"select user_id, %s from bindings where fingerprint = %Q;", strings_collect_cb, &other_user_ids, &err,
opt.tofu_db_format == TOFU_DB_SPLIT ? "email" : "policy", opt.tofu_db_format == TOFU_DB_SPLIT
fingerprint); ? "select user_id, email from bindings where fingerprint = ?;"
: "select user_id, policy from bindings where fingerprint = ?;",
SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_error (_("error gathering other user ids: %s.\n"), err); log_error (_("error gathering other user ids: %s.\n"), err);
@ -1605,8 +1897,9 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
/* XXX: When generating the statistics, do we want the time /* XXX: When generating the statistics, do we want the time
embedded in the signature (column 'sig_time') or the time that embedded in the signature (column 'sig_time') or the time that
we first verified the signature (column 'time'). */ we first verified the signature (column 'time'). */
rc = sqlite3_exec_printf rc = sqlite3_stepx
(db->db, signature_stats_collect_cb, &stats, &err, (db->db, &db->s.get_trust_gather_other_keys,
signature_stats_collect_cb, &stats, &err,
"select fingerprint, policy, time_ago, count(*)\n" "select fingerprint, policy, time_ago, count(*)\n"
" from (select bindings.*,\n" " from (select bindings.*,\n"
" case\n" " case\n"
@ -1615,27 +1908,30 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
small, medium or large units? (Note: whatever we do, we small, medium or large units? (Note: whatever we do, we
keep the value in seconds. Then when we group, everything keep the value in seconds. Then when we group, everything
that rounds to the same number of seconds is grouped.) */ that rounds to the same number of seconds is grouped.) */
" when delta < -%d then -1\n" " when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then -1\n"
" when delta < %d then max(0, round(delta / %d) * %d)\n" " when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n"
" when delta < %d then round(delta / %d) * %d\n" " then max(0,\n"
" else round(delta / %d) * %d\n" " round(delta / ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
" * ("STRINGIFY (TIME_AGO_UNIT_SMALL)"))\n"
" when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n"
" then round(delta / ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)"))\n"
" * ("STRINGIFY (TIME_AGO_UNIT_MEDIUM)")\n"
" else round(delta / ("STRINGIFY (TIME_AGO_UNIT_LARGE)"))\n"
" * ("STRINGIFY (TIME_AGO_UNIT_LARGE)")\n"
" end time_ago,\n" " end time_ago,\n"
" delta time_ago_raw\n" " delta time_ago_raw\n"
" from bindings\n" " from bindings\n"
" left join\n" " left join\n"
" (select *,\n" " (select *,\n"
" cast(strftime('%%s','now') - sig_time as real) delta\n" " cast(strftime('%s','now') - sig_time as real) delta\n"
" from signatures) ss\n" " from signatures) ss\n"
" on ss.binding = bindings.oid)\n" " on ss.binding = bindings.oid)\n"
" where email = %Q\n" " where email = ?\n"
" group by fingerprint, time_ago\n" " group by fingerprint, time_ago\n"
/* Make sure the current key is first. */ /* Make sure the current key is first. */
" order by fingerprint = %Q asc, fingerprint desc, time_ago desc;\n", " order by fingerprint = ? asc, fingerprint desc, time_ago desc;\n",
TIME_AGO_FUTURE_IGNORE, SQLITE_ARG_STRING, email, SQLITE_ARG_STRING, fingerprint,
TIME_AGO_MEDIUM_THRESHOLD, TIME_AGO_UNIT_SMALL, TIME_AGO_UNIT_SMALL, SQLITE_ARG_END);
TIME_AGO_LARGE_THRESHOLD, TIME_AGO_UNIT_MEDIUM, TIME_AGO_UNIT_MEDIUM,
TIME_AGO_UNIT_LARGE, TIME_AGO_UNIT_LARGE,
email, fingerprint);
if (rc) if (rc)
{ {
strlist_t strlist_iter; strlist_t strlist_iter;
@ -1825,7 +2121,7 @@ get_trust (struct db *dbs, const char *fingerprint, const char *email,
} }
static void static void
show_statistics (struct db *dbs, const char *fingerprint, show_statistics (struct dbs *dbs, const char *fingerprint,
const char *email, const char *user_id, const char *email, const char *user_id,
const char *sig_exclude) const char *sig_exclude)
{ {
@ -2174,7 +2470,7 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
const byte *sig_digest_bin, int sig_digest_bin_len, const byte *sig_digest_bin, int sig_digest_bin_len,
time_t sig_time, const char *origin, int may_ask) time_t sig_time, const char *origin, int may_ask)
{ {
struct db *dbs; struct dbs *dbs;
struct db *db; struct db *db;
char *fingerprint = NULL; char *fingerprint = NULL;
char *email = NULL; char *email = NULL;
@ -2228,7 +2524,9 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
/* We do a query and then an insert. Make sure they are atomic /* We do a query and then an insert. Make sure they are atomic
by wrapping them in a transaction. */ by wrapping them in a transaction. */
rc = sqlite3_exec (db->db, "begin transaction;", NULL, NULL, &err); rc = sqlite3_stepx (db->db, &db->s.begin_transaction,
NULL, NULL, &err, "begin transaction;",
SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_error (_("error beginning transaction on TOFU database: %s\n"), err); log_error (_("error beginning transaction on TOFU database: %s\n"), err);
@ -2238,14 +2536,18 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
/* If we've already seen this signature before, then don't add /* If we've already seen this signature before, then don't add
it again. */ it again. */
rc = sqlite3_exec_printf rc = sqlite3_stepx
(db->db, get_single_unsigned_long_cb, &c, &err, (db->db, &db->s.register_already_seen,
get_single_unsigned_long_cb, &c, &err,
"select count (*)\n" "select count (*)\n"
" from signatures left join bindings\n" " from signatures left join bindings\n"
" on signatures.binding = bindings.oid\n" " on signatures.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q and sig_time = 0x%lx\n" " where fingerprint = ? and email = ? and sig_time = ?\n"
" and sig_digest = %Q", " and sig_digest = ?",
fingerprint, email, (unsigned long) sig_time, sig_digest); SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_LONG_LONG, (long long) sig_time,
SQLITE_ARG_STRING, sig_digest,
SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_error (_("error reading from signatures database" log_error (_("error reading from signatures database"
@ -2281,15 +2583,18 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
assert (c == 0); assert (c == 0);
rc = sqlite3_exec_printf rc = sqlite3_stepx
(db->db, NULL, NULL, &err, (db->db, &db->s.register_insert, NULL, NULL, &err,
"insert into signatures\n" "insert into signatures\n"
" (binding, sig_digest, origin, sig_time, time)\n" " (binding, sig_digest, origin, sig_time, time)\n"
" values\n" " values\n"
" ((select oid from bindings\n" " ((select oid from bindings\n"
" where fingerprint = %Q and email = %Q),\n" " where fingerprint = ? and email = ?),\n"
" %Q, %Q, 0x%lx, strftime('%%s', 'now'));", " ?, ?, ?, strftime('%s', 'now'));",
fingerprint, email, sig_digest, origin, (unsigned long) sig_time); SQLITE_ARG_STRING, fingerprint, SQLITE_ARG_STRING, email,
SQLITE_ARG_STRING, sig_digest, SQLITE_ARG_STRING, origin,
SQLITE_ARG_LONG_LONG, (long long) sig_time,
SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_error (_("error updating TOFU DB" log_error (_("error updating TOFU DB"
@ -2302,9 +2607,11 @@ tofu_register (const byte *fingerprint_bin, const char *user_id,
/* It only matters whether we abort or commit the transaction /* It only matters whether we abort or commit the transaction
(so long as we do something) if we execute the insert. */ (so long as we do something) if we execute the insert. */
if (rc) if (rc)
rc = sqlite3_exec (db->db, "rollback;", NULL, NULL, &err); rc = sqlite3_stepx (db->db, &db->s.rollback, NULL, NULL, &err,
"rollback;", SQLITE_ARG_END);
else else
rc = sqlite3_exec (db->db, "commit transaction;", NULL, NULL, &err); rc = sqlite3_stepx (db->db, &db->s.end_transaction, NULL, NULL, &err,
"end transaction;", SQLITE_ARG_END);
if (rc) if (rc)
{ {
log_error (_("error ending transaction on TOFU database: %s\n"), err); log_error (_("error ending transaction on TOFU database: %s\n"), err);
@ -2392,7 +2699,7 @@ int
tofu_get_validity (const byte *fingerprint_bin, const char *user_id, tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
int may_ask) int may_ask)
{ {
struct db *dbs; struct dbs *dbs;
char *fingerprint = NULL; char *fingerprint = NULL;
char *email = NULL; char *email = NULL;
int trust_level = TRUST_UNDEFINED; int trust_level = TRUST_UNDEFINED;
@ -2441,7 +2748,7 @@ tofu_get_validity (const byte *fingerprint_bin, const char *user_id,
gpg_error_t gpg_error_t
tofu_set_policy (kbnode_t kb, enum tofu_policy policy) tofu_set_policy (kbnode_t kb, enum tofu_policy policy)
{ {
struct db *dbs; struct dbs *dbs;
PKT_public_key *pk; PKT_public_key *pk;
char fingerprint_bin[MAX_FINGERPRINT_LEN]; char fingerprint_bin[MAX_FINGERPRINT_LEN];
size_t fingerprint_bin_len = sizeof (fingerprint_bin); size_t fingerprint_bin_len = sizeof (fingerprint_bin);
@ -2524,7 +2831,7 @@ gpg_error_t
tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id, tofu_get_policy (PKT_public_key *pk, PKT_user_id *user_id,
enum tofu_policy *policy) enum tofu_policy *policy)
{ {
struct db *dbs; struct dbs *dbs;
char fingerprint_bin[MAX_FINGERPRINT_LEN]; char fingerprint_bin[MAX_FINGERPRINT_LEN];
size_t fingerprint_bin_len = sizeof (fingerprint_bin); size_t fingerprint_bin_len = sizeof (fingerprint_bin);
char *fingerprint; char *fingerprint;