summaryrefslogtreecommitdiff
path: root/pseudo_db.c
diff options
context:
space:
mode:
Diffstat (limited to 'pseudo_db.c')
-rw-r--r--pseudo_db.c2563
1 files changed, 2563 insertions, 0 deletions
diff --git a/pseudo_db.c b/pseudo_db.c
new file mode 100644
index 0000000..f60f0ab
--- /dev/null
+++ b/pseudo_db.c
@@ -0,0 +1,2563 @@
+/*
+ * pseudo_db.c, sqlite3 interface
+ *
+ * Copyright (c) 2008-2010,2013 Wind River Systems, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the Lesser GNU General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ *
+ * 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 Lesser GNU General Public License for more details.
+ *
+ * You should have received a copy of the Lesser GNU General Public License
+ * version 2.1 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sqlite3.h>
+
+#include "pseudo.h"
+#include "pseudo_ipc.h"
+#include "pseudo_db.h"
+
+/* #define NPROFILE */
+
+#ifdef NPROFILE
+void xProfile(void * pArg, const char * pQuery, sqlite3_uint64 pTimeTaken)
+{
+ pseudo_diag("profile: %lld %s\n", pTimeTaken, pQuery);
+}
+#endif
+
+struct log_history {
+ int rc;
+ unsigned long fields;
+ sqlite3_stmt *stmt;
+};
+
+struct pdb_file_list {
+ int rc;
+ sqlite3_stmt *stmt;
+};
+
+static int file_db_dirty = 0;
+#ifdef USE_MEMORY_DB
+static sqlite3 *real_file_db = 0;
+#endif
+static sqlite3 *file_db = 0;
+static sqlite3 *log_db = 0;
+
+/* What's going on here, you might well ask?
+ * This contains a template to build the database. I suppose maybe it
+ * should have been elegantly done as a big chunk of embedded SQL, but
+ * this looked like a good idea at the time.
+ */
+typedef struct { char *fmt; int arg; } id_row;
+
+/* This seemed like a really good idea at the time. The idea is that these
+ * structures let me write semi-abstract code to "create a database" without
+ * duplicating as much of the code.
+ */
+static struct sql_table {
+ char *name;
+ char *sql;
+ char **names;
+ id_row *values;
+} file_tables[] = {
+ { "files",
+ "id INTEGER PRIMARY KEY, "
+ "path VARCHAR, "
+ "dev INTEGER, "
+ "ino INTEGER, "
+ "uid INTEGER, "
+ "gid INTEGER, "
+ "mode INTEGER, "
+ "rdev INTEGER",
+ NULL,
+ NULL },
+ { "xattrs",
+ "id INTEGER PRIMARY KEY, "
+ "file_id INTEGER REFERENCES files(id) ON DELETE CASCADE, "
+ "name VARCHAR, "
+ "value VARCHAR",
+ NULL,
+ NULL },
+ { NULL, NULL, NULL, NULL },
+}, log_tables[] = {
+ { "logs",
+ "id INTEGER PRIMARY KEY, "
+ "stamp INTEGER, "
+ "op INTEGER, "
+ "client INTEGER, "
+ "fd INTEGER, "
+ "dev INTEGER, "
+ "ino INTEGER, "
+ "mode INTEGER, "
+ "path VARCHAR, "
+ "result INTEGER, "
+ "severity INTEGER, "
+ "text VARCHAR ",
+ NULL,
+ NULL },
+ { NULL, NULL, NULL, NULL },
+};
+
+/* similarly, this creates indexes generically. */
+static struct sql_index {
+ char *name;
+ char *table;
+ char *keys;
+} file_indexes[] = {
+/* { "files__path", "files", "path" }, */
+ { "files__path_dev_ino", "files", "path, dev, ino" },
+ { "files__dev_ino", "files", "dev, ino" },
+ { "xattrs__file", "xattrs", "file_id" },
+ { NULL, NULL, NULL },
+}, log_indexes[] = {
+ { NULL, NULL, NULL },
+};
+
+static char *file_pragmas[] = {
+ "PRAGMA legacy_file_format = OFF;",
+ "PRAGMA journal_mode = OFF;",
+ /* the default page size produces painfully bad behavior
+ * for memory databases with some versions of sqlite.
+ */
+ "PRAGMA page_size = 8192;",
+ "PRAGMA locking_mode = EXCLUSIVE;",
+ /* Setting this to NORMAL makes pseudo noticably slower
+ * than fakeroot, but is perhaps more secure. However,
+ * note that sqlite always flushes to the OS; what is lacking
+ * in non-synchronous mode is waiting for the OS to
+ * confirm delivery to media, and also a bunch of cache
+ * flushing and reloading which we probably don't really
+ * need.
+ */
+ "PRAGMA synchronous = OFF;",
+ "PRAGMA foreign_keys = ON;",
+ NULL
+};
+
+static char *log_pragmas[] = {
+ "PRAGMA legacy_file_format = OFF;",
+ "PRAGMA journal_mode = OFF;",
+ "PRAGMA locking_mode = EXCLUSIVE;",
+ "PRAGMA synchronous = OFF;",
+ NULL
+};
+
+/* table migrations: */
+/* If there is no migration table, we assume "version -1" -- the
+ * version shipped with wrlinux 3.0, which had no version
+ * number. Otherwise, we check it for the highest version recorded.
+ * We then perform, and then record, each migration in sequence.
+ * The first migration is the migration to create the migrations
+ * table; this way, it'll work on existing databases. It'll also
+ * work for new databases -- the migrations get performed in order
+ * before the databases are considered to be set up.
+ */
+
+static char create_migration_table[] =
+ "CREATE TABLE migrations ("
+ "id INTEGER PRIMARY KEY, "
+ "version INTEGER, "
+ "stamp INTEGER, "
+ "sql VARCHAR"
+ ");";
+static char index_migration_table[] =
+ "CREATE INDEX migration__version ON migrations (version)";
+
+/* This used to be a { version, sql } pair, but version was always
+ * the same as index into the table, so I removed it.
+ * The first migration in each database is migration #0 -- the
+ * creation of the migration table now being used for versioning.
+ * The second is indexing on version -- sqlite3 can grab MAX(version)
+ * faster if it's indexed. (Indexing this table is very cheap, since
+ * there are very few migrations and each one produces exactly
+ * one insert.)
+ */
+static struct sql_migration {
+ char *sql;
+} file_migrations[] = {
+ { create_migration_table },
+ { index_migration_table },
+ { "ALTER TABLE files ADD deleting INTEGER;" },
+ { NULL },
+}, log_migrations[] = {
+ { create_migration_table },
+ { index_migration_table },
+ /* support for hostdeps merge -- this allows us to log "tags"
+ * along with events.
+ */
+ { "ALTER TABLE logs ADD tag VARCHAR;" },
+ /* the logs table was defined so early I hadn't realized I cared
+ * about UID and GID.
+ */
+ { "ALTER TABLE logs ADD uid INTEGER;" },
+ { "ALTER TABLE logs ADD gid INTEGER;" },
+ /* track access types (read/write, etc) */
+ { "ALTER TABLE logs ADD access INTEGER;" },
+ /* client program/path */
+ { "ALTER TABLE logs ADD program VARCHAR;" },
+ /* message type (ping, op) */
+ { "ALTER TABLE logs ADD type INTEGER;" },
+ { NULL },
+};
+
+/* cleanup database before getting started
+ *
+ * On a large build, the logs database gets GIGANTIC... And
+ * we rarely-if-ever delete things from it. So instead of
+ * doing the vacuum operation on it at startup, which can impose
+ * a several-minute delay, we do it only on deletions.
+ *
+ * There's no setup for log database right now.
+ */
+char *file_setups[] = {
+ "VACUUM;",
+ NULL,
+};
+
+struct database_info {
+ char *pathname;
+ struct sql_index *indexes;
+ struct sql_table *tables;
+ struct sql_migration *migrations;
+ char **pragmas;
+ char **setups;
+ struct sqlite3 **db;
+};
+
+static struct database_info db_infos[] = {
+ {
+ "logs.db",
+ log_indexes,
+ log_tables,
+ log_migrations,
+ log_pragmas,
+ NULL,
+ &log_db
+ },
+ {
+ "files.db",
+ file_indexes,
+ file_tables,
+ file_migrations,
+ file_pragmas,
+ file_setups,
+#ifdef USE_MEMORY_DB
+ &real_file_db
+ },
+ {
+ ":memory:",
+ file_indexes,
+ file_tables,
+ file_migrations,
+ file_pragmas,
+ file_setups,
+#endif
+ &file_db
+ },
+ { 0, 0, 0, 0, 0, 0, 0 }
+};
+
+/* pretty-print error along with the underlying SQL error. */
+static void
+dberr(sqlite3 *db, char *fmt, ...) {
+ va_list ap;
+ char debuff[8192];
+ int len;
+
+ va_start(ap, fmt);
+ len = vsnprintf(debuff, 8192, fmt, ap);
+ va_end(ap);
+ len = write(pseudo_util_debug_fd, debuff, len);
+ if (db) {
+ len = snprintf(debuff, 8192, ": %s\n", sqlite3_errmsg(db));
+ len = write(pseudo_util_debug_fd, debuff, len);
+ } else {
+ len = write(pseudo_util_debug_fd, " (no db)\n", 9);
+ }
+}
+
+#ifdef USE_MEMORY_DB
+
+static void
+pdb_backup() {
+ sqlite3_backup *pBackup;
+ /* no point in doing this if we don't have a database to back up,
+ * or nothing's changed.
+ */
+ if (!file_db || !real_file_db || !file_db_dirty)
+ return;
+
+ pBackup = sqlite3_backup_init(real_file_db, "main", file_db, "main");
+ if (pBackup) {
+ int rc;
+ (void)sqlite3_backup_step(pBackup, -1);
+ rc = sqlite3_backup_finish(pBackup);
+ if (rc != SQLITE_OK) {
+ dberr(real_file_db, "error during flush to disk");
+ }
+ }
+ file_db_dirty = 0;
+}
+
+static void
+pdb_restore() {
+ sqlite3_backup *pBackup;
+ /* no point in doing this if we don't have a database to back up */
+ if (!file_db || !real_file_db)
+ return;
+
+ pBackup = sqlite3_backup_init(file_db, "main", real_file_db, "main");
+ if (pBackup) {
+ int rc;
+ (void)sqlite3_backup_step(pBackup, -1);
+ rc = sqlite3_backup_finish(pBackup);
+ if (rc != SQLITE_OK) {
+ dberr(file_db, "error during load from disk");
+ }
+ }
+ file_db_dirty = 0;
+}
+
+int
+pdb_maybe_backup(void) {
+ static int occasional = 0;
+ if (file_db && real_file_db) {
+ occasional = (occasional + 1) % 10;
+ if (occasional == 0) {
+ pdb_backup();
+ return 1;
+ }
+ }
+ return 0;
+}
+#else /* USE_MEMORY_DB */
+int
+pdb_maybe_backup(void) {
+ return 0;
+}
+#endif
+
+/* those who enjoy children, sausages, and databases, should not watch
+ * them being made.
+ */
+static int
+make_tables(sqlite3 *db,
+ struct sql_table *sql_tables,
+ struct sql_index *sql_indexes,
+ struct sql_migration *sql_migrations,
+ char **existing, int rows) {
+
+ static sqlite3_stmt *stmt;
+ sqlite3_stmt *update_version = 0;
+ struct sql_migration *m;
+ int available_migrations;
+ int version = -1;
+ int i, j;
+ char *sql;
+ char *errmsg;
+ int rc;
+ int found = 0;
+
+ for (i = 0; sql_tables[i].name; ++i) {
+ found = 0;
+ printf("considering table %s\n", sql_tables[i].name);
+ for (j = 1; j <= rows; ++j) {
+ if (!strcmp(existing[j], sql_tables[i].name)) {
+ found = 1;
+ break;
+ }
+ }
+ if (found)
+ continue;
+ /* now to create the table */
+ sql = sqlite3_mprintf("CREATE TABLE %s ( %s );",
+ sql_tables[i].name, sql_tables[i].sql);
+ rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
+ sqlite3_free(sql);
+ if (rc) {
+ dberr(db, "error trying to create %s", sql_tables[i].name);
+ return 1;
+ }
+ if (sql_tables[i].values) {
+ for (j = 0; sql_tables[i].values[j].fmt; ++j) {
+ char buffer[256];
+ snprintf(buffer, sizeof(buffer), sql_tables[i].values[j].fmt, sql_tables[i].values[j].arg);
+ sql = sqlite3_mprintf("INSERT INTO %s ( %s ) VALUES ( %s );",
+ sql_tables[i].name,
+ *sql_tables[i].names,
+ buffer);
+ rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
+ sqlite3_free(sql);
+ if (rc) {
+ dberr(db, "error trying to populate %s",
+ sql_tables[i].name);
+ return 1;
+ }
+ }
+ }
+ for (j = 0; sql_indexes[j].name; ++j) {
+ if (strcmp(sql_indexes[j].table, sql_tables[i].name))
+ continue;
+ sql = sqlite3_mprintf("CREATE INDEX %s ON %s ( %s );",
+ sql_indexes[j].name,
+ sql_indexes[j].table,
+ sql_indexes[j].keys);
+ rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
+ sqlite3_free(sql);
+ if (rc) {
+ dberr(db, "error trying to index %s",
+ sql_tables[i].name);
+ return 1;
+ }
+ }
+ }
+ /* now, see about migrations */
+ found = 0;
+ for (j = 1; j <= rows; ++j) {
+ if (!strcmp(existing[j], "migrations")) {
+ found = 1;
+ break;
+ }
+ }
+ if (found) {
+ sql = "SELECT MAX(version) FROM migrations;";
+ rc = sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, NULL);
+ if (rc) {
+ dberr(db, "couldn't examine migrations table");
+ return 1;
+ }
+ rc = sqlite3_step(stmt);
+ if (rc == SQLITE_ROW) {
+ version = (unsigned long) sqlite3_column_int64(stmt, 0);
+ rc = sqlite3_step(stmt);
+ } else {
+ version = -1;
+ }
+ if (rc != SQLITE_DONE) {
+ dberr(db, "not done after the single row we expected?", rc);
+ return 1;
+ }
+ pseudo_debug(PDBGF_DB, "existing database version: %d\n", version);
+ rc = sqlite3_finalize(stmt);
+ if (rc) {
+ dberr(db, "couldn't finalize version check");
+ return 1;
+ }
+ } else {
+ pseudo_debug(PDBGF_DB, "no existing database version\n");
+ version = -1;
+ }
+ for (m = sql_migrations; m->sql; ++m)
+ ;
+ available_migrations = m - sql_migrations;
+ /* I am pretty sure this can never happen. */
+ if (version < -1)
+ version = -1;
+ /* I hope this can never happen. */
+ if (version >= available_migrations)
+ version = available_migrations - 1;
+ for (m = sql_migrations + (version + 1); m->sql; ++m) {
+ int migration = (m - sql_migrations);
+ pseudo_debug(PDBGF_DB, "considering migration %d\n", migration);
+ if (version >= migration)
+ continue;
+ pseudo_debug(PDBGF_DB, "running migration %d\n", migration);
+ rc = sqlite3_prepare_v2(db,
+ m->sql,
+ strlen(m->sql),
+ &stmt, NULL);
+ if (rc) {
+ dberr(log_db, "couldn't prepare migration %d (%s)",
+ migration, m->sql);
+ return 1;
+ }
+ rc = sqlite3_step(stmt);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "migration %d failed",
+ migration);
+ return 1;
+ }
+ sqlite3_finalize(stmt);
+ /* this has to occur here, because the first migration
+ * executed CREATES the migration table, so you can't
+ * prepare this statement if you haven't already executed
+ * the first migration.
+ *
+ * Lesson learned: Yes, it actually WILL be a sort of big
+ * deal to add versioning later.
+ */
+ static char *update_sql =
+ "INSERT INTO migrations ("
+ "version, stamp, sql"
+ ") VALUES (?, ?, ?);";
+ rc = sqlite3_prepare_v2(db,
+ update_sql,
+ strlen(update_sql),
+ &update_version, NULL);
+ if (rc) {
+ dberr(db, "couldn't prepare statement to update migrations");
+ return 1;
+ }
+ sqlite3_bind_int(update_version, 1, migration);
+ sqlite3_bind_int(update_version, 2, time(NULL));
+ sqlite3_bind_text(update_version, 3, m->sql, -1, SQLITE_STATIC);
+ rc = sqlite3_step(update_version);
+ if (rc != SQLITE_DONE) {
+ dberr(db, "couldn't update migrations table (after migration to version %d)",
+ migration);
+ sqlite3_finalize(update_version);
+ return 1;
+ } else {
+ pseudo_debug(PDBGF_DB, "update of migrations (after %d) fine.\n",
+ migration);
+ }
+ sqlite3_finalize(update_version);
+ update_version = 0;
+ version = migration;
+ }
+
+ return 0;
+}
+
+/* registered with atexit */
+static void
+cleanup_db(void) {
+ pseudo_debug(PDBGF_SERVER, "server exiting\n");
+#ifdef USE_MEMORY_DB
+ if (real_file_db) {
+ pdb_backup();
+ sqlite3_close(real_file_db);
+ }
+#endif
+ if (file_db)
+ sqlite3_close(file_db);
+ if (log_db)
+ sqlite3_close(log_db);
+}
+
+/* This function has been rewritten and I no longer hate it. I just feel
+ * like it's important to say that.
+ */
+static int
+get_db(struct database_info *dbinfo) {
+ int rc;
+ int i;
+ char *sql;
+ char **results;
+ int rows, columns;
+ char *errmsg;
+ static int registered_cleanup = 0;
+ char *dbfile;
+ sqlite3 *db;
+
+ if (!dbinfo)
+ return 1;
+ if (!dbinfo->db)
+ return 1;
+ /* this database is perhaps already initialized? */
+ if (*(dbinfo->db))
+ return 0;
+
+ dbfile = pseudo_localstatedir_path(dbinfo->pathname);
+#ifdef USE_MEMORY_DB
+ if (!strcmp(dbinfo->pathname, ":memory:")) {
+ rc = sqlite3_open(dbinfo->pathname, &db);
+ } else
+#endif
+ rc = sqlite3_open(dbfile, &db);
+ free(dbfile);
+ if (rc) {
+ pseudo_diag("Failed: %s\n", sqlite3_errmsg(db));
+ sqlite3_close(db);
+ *(dbinfo->db) = NULL;
+ return 1;
+ }
+ /* we store this in the database_info, but hereafter we'll just use
+ * the name db, because it is shorter.
+ */
+ *dbinfo->db = db;
+ if (!registered_cleanup) {
+ atexit(cleanup_db);
+ registered_cleanup = 1;
+ }
+ if (dbinfo->pragmas) {
+ for (i = 0; dbinfo->pragmas[i]; ++i) {
+ rc = sqlite3_exec(db, dbinfo->pragmas[i], NULL, NULL, &errmsg);
+ pseudo_debug(PDBGF_SQL | PDBGF_VERBOSE, "executed pragma: '%s', rc %d.\n",
+ dbinfo->pragmas[i], rc);
+ if (rc) {
+ dberr(db, dbinfo->pragmas[i]);
+ }
+ }
+ }
+ /* create database tables or die trying */
+ sql = "SELECT name FROM sqlite_master "
+ "WHERE type = 'table' "
+ "ORDER BY name;";
+ rc = sqlite3_get_table(db, sql, &results, &rows, &columns, &errmsg);
+ if (rc) {
+ pseudo_diag("Failed: %s\n", errmsg);
+ } else {
+ rc = make_tables(db, dbinfo->tables, dbinfo->indexes, dbinfo->migrations, results, rows);
+ sqlite3_free_table(results);
+ }
+ /* as of now, the only setup is a vacuum operation which we don't care about
+ * the results of.
+ */
+ if (dbinfo->setups) {
+ for (i = 0; dbinfo->setups[i]; ++i) {
+ sqlite3_exec(db, dbinfo->setups[i], NULL, NULL, &errmsg);
+ }
+ }
+ return rc;
+}
+
+static int
+get_dbs(void) {
+ int err = 0;
+ int i;
+#ifdef USE_MEMORY_DB
+ int already_loaded = 0;
+ if (file_db)
+ already_loaded = 1;
+#endif
+ for (i = 0; db_infos[i].db; ++i) {
+ if (get_db(&db_infos[i])) {
+ pseudo_diag("Error getting '%s' database.\n",
+ db_infos[i].pathname);
+ err = 1;
+ }
+ }
+#ifdef USE_MEMORY_DB
+ if (!already_loaded && file_db)
+ pdb_restore();
+#endif
+ return err;
+}
+
+/* put a prepared log entry into the database */
+int
+pdb_log_traits(pseudo_query_t *traits) {
+ pseudo_query_t *trait;
+ log_entry *e;
+ int rc;
+
+ if (!log_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 1;
+ }
+ e = calloc(sizeof(*e), 1);
+ if (!e) {
+ pseudo_diag("can't allocate space for log entry.");
+ return 1;
+ }
+ for (trait = traits; trait; trait = trait->next) {
+ switch (trait->field) {
+ case PSQF_ACCESS:
+ e->access = trait->data.ivalue;
+ break;
+ case PSQF_CLIENT:
+ e->client = trait->data.ivalue;
+ break;
+ case PSQF_DEV:
+ e->dev = trait->data.ivalue;
+ break;
+ case PSQF_FD:
+ e->fd = trait->data.ivalue;
+ break;
+ case PSQF_FTYPE:
+ e->mode |= (trait->data.ivalue & S_IFMT);
+ break;
+ case PSQF_GID:
+ e->gid = trait->data.ivalue;
+ break;
+ case PSQF_INODE:
+ e->ino = trait->data.ivalue;
+ break;
+ case PSQF_MODE:
+ e->mode = trait->data.ivalue;
+ break;
+ case PSQF_OP:
+ e->op = trait->data.ivalue;
+ break;
+ case PSQF_PATH:
+ e->path = trait->data.svalue ?
+ strdup(trait->data.svalue) : NULL;
+ break;
+ case PSQF_PERM:
+ e->mode |= (trait->data.ivalue & ~(S_IFMT) & 0177777);
+ break;
+ case PSQF_PROGRAM:
+ e->program = trait->data.svalue ?
+ strdup(trait->data.svalue) : NULL;
+ break;
+ case PSQF_RESULT:
+ e->result = trait->data.ivalue;
+ break;
+ case PSQF_SEVERITY:
+ e->severity = trait->data.ivalue;
+ break;
+ case PSQF_STAMP:
+ e->stamp = trait->data.ivalue;
+ break;
+ case PSQF_TAG:
+ e->tag = trait->data.svalue ?
+ strdup(trait->data.svalue) : NULL;
+ break;
+ case PSQF_TEXT:
+ e->text = trait->data.svalue ?
+ strdup(trait->data.svalue) : NULL;
+ break;
+ case PSQF_TYPE:
+ e->type = trait->data.ivalue;
+ break;
+ case PSQF_UID:
+ e->uid = trait->data.ivalue;
+ break;
+ case PSQF_ID:
+ case PSQF_ORDER:
+ default:
+ pseudo_diag("Invalid trait %s for log creation.\n",
+ pseudo_query_field_name(trait->field));
+ free(e);
+ return 1;
+ break;
+ }
+ }
+ rc = pdb_log_entry(e);
+ log_entry_free(e);
+ return rc;
+}
+
+/* create a log from a given log entry, with tag and text */
+int
+pdb_log_entry(log_entry *e) {
+ char *sql = "INSERT INTO logs "
+ "(stamp, op, access, client, dev, gid, ino, mode, path, result, severity, text, program, tag, type, uid)"
+ " VALUES "
+ "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+ static sqlite3_stmt *insert;
+ int field;
+ int rc;
+
+ if (!log_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 1;
+ }
+
+ if (!insert) {
+ rc = sqlite3_prepare_v2(log_db, sql, strlen(sql), &insert, NULL);
+ if (rc) {
+ dberr(log_db, "couldn't prepare INSERT statement");
+ return 1;
+ }
+ }
+
+ field = 1;
+ if (e) {
+ if (e->stamp) {
+ sqlite3_bind_int(insert, field++, e->stamp);
+ } else {
+ sqlite3_bind_int(insert, field++, (unsigned long) time(NULL));
+ }
+ sqlite3_bind_int(insert, field++, e->op);
+ sqlite3_bind_int(insert, field++, e->access);
+ sqlite3_bind_int(insert, field++, e->client);
+ sqlite3_bind_int(insert, field++, e->dev);
+ sqlite3_bind_int(insert, field++, e->gid);
+ sqlite3_bind_int64(insert, field++, e->ino);
+ sqlite3_bind_int(insert, field++, e->mode);
+ if (e->path) {
+ sqlite3_bind_text(insert, field++, e->path, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ sqlite3_bind_int(insert, field++, e->result);
+ sqlite3_bind_int(insert, field++, e->severity);
+ if (e->text) {
+ sqlite3_bind_text(insert, field++, e->text, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ if (e->program) {
+ sqlite3_bind_text(insert, field++, e->program, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ if (e->tag) {
+ sqlite3_bind_text(insert, field++, e->tag, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ sqlite3_bind_int(insert, field++, e->type);
+ sqlite3_bind_int(insert, field++, e->uid);
+ } else {
+ sqlite3_bind_int(insert, field++, (unsigned long) time(NULL));
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_null(insert, field++);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_null(insert, field++);
+ sqlite3_bind_null(insert, field++);
+ sqlite3_bind_null(insert, field++);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ }
+
+ rc = sqlite3_step(insert);
+ if (rc != SQLITE_DONE) {
+ dberr(log_db, "insert may have failed");
+ }
+ sqlite3_reset(insert);
+ sqlite3_clear_bindings(insert);
+ return rc != SQLITE_DONE;
+}
+/* create a log from a given message, with tag and text */
+int
+pdb_log_msg(pseudo_sev_t severity, pseudo_msg_t *msg, const char *program, const char *tag, const char *text, ...) {
+ char *sql = "INSERT INTO logs "
+ "(stamp, op, access, client, dev, gid, ino, mode, path, result, uid, severity, text, program, tag, type)"
+ " VALUES "
+ "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
+ static sqlite3_stmt *insert;
+ char buffer[8192];
+ int field;
+ int rc;
+ va_list ap;
+
+ if (text) {
+ va_start(ap, text);
+ vsnprintf(buffer, 8192, text, ap);
+ va_end(ap);
+ text = buffer;
+ }
+
+ if (!log_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 1;
+ }
+
+ if (!insert) {
+ rc = sqlite3_prepare_v2(log_db, sql, strlen(sql), &insert, NULL);
+ if (rc) {
+ dberr(log_db, "couldn't prepare INSERT statement");
+ return 1;
+ }
+ }
+
+ field = 1;
+ sqlite3_bind_int(insert, field++, (unsigned long) time(NULL));
+ if (msg) {
+ sqlite3_bind_int(insert, field++, msg->op);
+ sqlite3_bind_int(insert, field++, msg->access);
+ sqlite3_bind_int(insert, field++, msg->client);
+ sqlite3_bind_int(insert, field++, msg->dev);
+ sqlite3_bind_int(insert, field++, msg->gid);
+ sqlite3_bind_int64(insert, field++, msg->ino);
+ sqlite3_bind_int(insert, field++, msg->mode);
+ if (msg->pathlen) {
+ sqlite3_bind_text(insert, field++, msg->path, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ sqlite3_bind_int(insert, field++, msg->result);
+ sqlite3_bind_int(insert, field++, msg->uid);
+ } else {
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_null(insert, field++);
+ sqlite3_bind_int(insert, field++, 0);
+ sqlite3_bind_int(insert, field++, 0);
+ }
+ sqlite3_bind_int(insert, field++, severity);
+ if (text) {
+ sqlite3_bind_text(insert, field++, text, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ if (program) {
+ sqlite3_bind_text(insert, field++, program, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ if (tag) {
+ sqlite3_bind_text(insert, field++, tag, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null(insert, field++);
+ }
+ if (msg) {
+ sqlite3_bind_int(insert, field++, msg->type);
+ } else {
+ sqlite3_bind_int(insert, field++, 0);
+ }
+
+ rc = sqlite3_step(insert);
+ if (rc != SQLITE_DONE) {
+ dberr(log_db, "insert may have failed");
+ }
+ sqlite3_reset(insert);
+ sqlite3_clear_bindings(insert);
+ return rc != SQLITE_DONE;
+}
+
+#define BFSZ 8192
+typedef struct {
+ size_t buflen;
+ char *data;
+ char *tail;
+} buffer;
+
+static int
+frag(buffer *b, char *fmt, ...) {
+ va_list ap;
+ static size_t curlen;
+ int rc;
+
+ if (!b) {
+ pseudo_diag("frag called without buffer.\n");
+ return -1;
+ }
+ curlen = b->tail - b->data;
+ va_start(ap, fmt);
+ rc = vsnprintf(b->tail, b->buflen - curlen, fmt, ap);
+ va_end(ap);
+ if ((rc > 0) && ((size_t) rc >= (b->buflen - curlen))) {
+ size_t newlen = b->buflen;
+ while (newlen <= (rc + curlen))
+ newlen *= 2;
+ char *newbuf = malloc(newlen);
+ if (!newbuf) {
+ pseudo_diag("failed to allocate SQL buffer.\n");
+ return -1;
+ }
+ memcpy(newbuf, b->data, curlen + 1);
+ b->tail = newbuf + curlen;
+ free(b->data);
+ b->data = newbuf;
+ b->buflen = newlen;
+ /* try again */
+ va_start(ap, fmt);
+ rc = vsnprintf(b->tail, b->buflen - curlen, fmt, ap);
+ va_end(ap);
+ if ((rc > 0) && ((size_t) rc >= (b->buflen - curlen))) {
+ pseudo_diag("tried to reallocate larger buffer, failed. giving up.\n");
+ return -1;
+ }
+ }
+ if (rc >= 0)
+ b->tail += rc;
+ return rc;
+}
+
+sqlite3_stmt *
+pdb_query(char *stmt_type, pseudo_query_t *traits, unsigned long fields, int unique, int want_results) {
+ pseudo_query_t *trait;
+ sqlite3_stmt *stmt;
+ int done_any = 0;
+ int field = 0;
+ const char *order_by = "id";
+ char *order_dir = "ASC";
+ int rc;
+ pseudo_query_field_t f;
+ static buffer *sql;
+
+ if (!log_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return NULL;
+ }
+
+ if (!stmt_type) {
+ pseudo_diag("can't prepare a statement without a type.\n");
+ }
+
+ if (!sql) {
+ sql = malloc(sizeof *sql);
+ if (!sql) {
+ pseudo_diag("can't allocate SQL buffer.\n");
+ return NULL;
+ }
+ sql->buflen = 512;
+ sql->data = malloc(sql->buflen);
+ if (!sql->data) {
+ pseudo_diag("can't allocate SQL text buffer.\n");
+ free(sql);
+ sql = 0;
+ return NULL;
+ }
+ }
+ sql->tail = sql->data;
+ /* should be DELETE or SELECT */
+ frag(sql, "%s ", stmt_type);
+
+ if (want_results) {
+ if (unique)
+ frag(sql, "DISTINCT ");
+
+ done_any = 0;
+ for (f = PSQF_NONE + 1; f < PSQF_MAX; ++f) {
+ if (fields & (1 << f)) {
+ frag(sql, "%s%s",
+ done_any ? ", " : "",
+ pseudo_query_field_name(f));
+ done_any = 1;
+ }
+ }
+ }
+
+ frag(sql, " FROM logs ");
+ /* first, build up an SQL string with the fields and operators */
+ done_any = 0;
+ for (trait = traits; trait; trait = trait->next) {
+ if (trait->field != PSQF_ORDER) {
+ if (done_any) {
+ frag(sql, "AND ");
+ } else {
+ frag(sql, "WHERE ");
+ done_any = 1;
+ }
+ }
+ switch (trait->field) {
+ case PSQF_PROGRAM:
+ case PSQF_TEXT:
+ case PSQF_TAG:
+ case PSQF_PATH:
+ switch (trait->type) {
+ case PSQT_LIKE:
+ frag(sql, "%s %s ('%' || ? || '%')",
+ pseudo_query_field_name(trait->field),
+ pseudo_query_type_sql(trait->type));
+ break;
+ case PSQT_NOTLIKE:
+ case PSQT_SQLPAT:
+ frag(sql, "%s %s ?",
+ pseudo_query_field_name(trait->field),
+ pseudo_query_type_sql(trait->type));
+ break;
+ default:
+ frag(sql, "%s %s ? ",
+ pseudo_query_field_name(trait->field),
+ pseudo_query_type_sql(trait->type));
+ break;
+ }
+ break;
+ case PSQF_PERM:
+ switch (trait->type) {
+ case PSQT_LIKE:
+ case PSQT_NOTLIKE:
+ case PSQT_SQLPAT:
+ pseudo_diag("Error: Can't use a LIKE match on non-text fields.\n");
+ return 0;
+ break;
+ default:
+ break;
+ }
+ /* mask out permission bits */
+ frag(sql, "(%s & %d) %s ? ",
+ "mode",
+ ~(S_IFMT) & 0177777,
+ pseudo_query_type_sql(trait->type));
+ break;
+ case PSQF_FTYPE:
+ switch (trait->type) {
+ case PSQT_LIKE:
+ case PSQT_NOTLIKE:
+ case PSQT_SQLPAT:
+ pseudo_diag("Error: Can't use a LIKE match on non-text fields.\n");
+ return 0;
+ break;
+ default:
+ break;
+ }
+ /* mask out permission bits */
+ frag(sql, "(%s & %d) %s ? ",
+ "mode",
+ S_IFMT,
+ pseudo_query_type_sql(trait->type));
+ break;
+ case PSQF_ORDER:
+ order_by = pseudo_query_field_name(trait->data.ivalue);
+ switch (trait->type) {
+ case PSQT_LESS:
+ order_dir = "DESC";
+ break;
+ case PSQT_EXACT:
+ /* this was already the default */
+ break;
+ case PSQT_GREATER:
+ order_dir = "ASC";
+ break;
+ default:
+ pseudo_diag("Ordering must be < or >.\n");
+ return 0;
+ break;
+ }
+ break;
+ default:
+ switch (trait->type) {
+ case PSQT_LIKE:
+ case PSQT_NOTLIKE:
+ case PSQT_SQLPAT:
+ pseudo_diag("Error: Can't use a LIKE match on non-text fields.\n");
+ return 0;
+ break;
+ default:
+ frag(sql, "%s %s ? ",
+ pseudo_query_field_name(trait->field),
+ pseudo_query_type_sql(trait->type));
+ break;
+ }
+ break;
+ }
+ }
+ if (want_results)
+ frag(sql, "ORDER BY %s %s;", order_by, order_dir);
+ pseudo_debug(PDBGF_SQL, "created SQL: <%s>\n", sql->data);
+
+ /* second, prepare it */
+ rc = sqlite3_prepare_v2(log_db, sql->data, strlen(sql->data), &stmt, NULL);
+ if (rc) {
+ dberr(log_db, "couldn't prepare %s statement", stmt_type);
+ return NULL;
+ }
+
+ /* third, bind the fields */
+ field = 1;
+ for (trait = traits; trait; trait = trait->next) {
+ switch (trait->field) {
+ case PSQF_ORDER:
+ /* this just creates a hunk of SQL above */
+ break;
+ case PSQF_PROGRAM:
+ case PSQF_PATH:
+ case PSQF_TAG:
+ case PSQF_TEXT:
+ sqlite3_bind_text(stmt, field++,
+ trait->data.svalue, -1, SQLITE_STATIC);
+ break;
+ case PSQF_ACCESS:
+ case PSQF_CLIENT:
+ case PSQF_DEV:
+ case PSQF_FD:
+ case PSQF_FTYPE:
+ case PSQF_INODE:
+ case PSQF_GID:
+ case PSQF_PERM:
+ case PSQF_MODE:
+ case PSQF_OP:
+ case PSQF_RESULT:
+ case PSQF_SEVERITY:
+ case PSQF_STAMP:
+ case PSQF_TYPE:
+ case PSQF_UID:
+ sqlite3_bind_int64(stmt, field++, trait->data.ivalue);
+ break;
+ default:
+ pseudo_diag("Inexplicably invalid field type %d\n", trait->field);
+ sqlite3_finalize(stmt);
+ return NULL;
+ }
+ }
+ return stmt;
+}
+
+int
+pdb_delete(pseudo_query_t *traits, unsigned long fields) {
+ sqlite3_stmt *stmt;
+
+ stmt = pdb_query("DELETE", traits, fields, 0, 0);
+
+ /* no need to return it, so... */
+ if (stmt) {
+ file_db_dirty = 1;
+ int rc = sqlite3_step(stmt);
+ if (rc != SQLITE_DONE) {
+ dberr(log_db, "deletion failed");
+ return -1;
+ } else {
+ pseudo_diag("Deleted records, vacuuming log database (may take a while).\n");
+ /* we can't do anything about it if this fails... */
+ sqlite3_exec(log_db, "VACUUM;", NULL, NULL, NULL);
+ }
+ sqlite3_finalize(stmt);
+ return 0;
+ }
+ return -1;
+}
+
+log_history
+pdb_history(pseudo_query_t *traits, unsigned long fields, int unique) {
+ log_history h = NULL;
+ sqlite3_stmt *stmt;
+
+ stmt = pdb_query("SELECT", traits, fields, unique, 1);
+
+ if (stmt) {
+ /* fourth, return the statement, now ready to be stepped through */
+ h = malloc(sizeof(*h));
+ if (h) {
+ h->rc = 0;
+ h->fields = fields;
+ h->stmt = stmt;
+ } else {
+ pseudo_diag("failed to allocate memory for log_history\n");
+ sqlite3_finalize(stmt);
+ }
+ return h;
+ } else {
+ return NULL;
+ }
+}
+
+log_entry *
+pdb_history_entry(log_history h) {
+ log_entry *l;
+ const unsigned char *s;
+ int column;
+ pseudo_query_field_t f;
+
+ if (!h || !h->stmt)
+ return 0;
+ /* in case someone tries again after we're already done */
+ if (h->rc == SQLITE_DONE) {
+ return 0;
+ }
+ h->rc = sqlite3_step(h->stmt);
+ if (h->rc == SQLITE_DONE) {
+ return 0;
+ } else if (h->rc != SQLITE_ROW) {
+ dberr(log_db, "statement failed");
+ return 0;
+ }
+ l = calloc(sizeof(log_entry), 1);
+ if (!l) {
+ pseudo_diag("couldn't allocate log entry.\n");
+ return 0;
+ }
+
+ column = 0;
+ for (f = PSQF_NONE + 1; f < PSQF_MAX; ++f) {
+ if (!(h->fields & (1 << f)))
+ continue;
+ switch (f) {
+ case PSQF_ACCESS:
+ l->access = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_CLIENT:
+ l->client = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_DEV:
+ l->dev = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_FD:
+ l->fd = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_GID:
+ l->gid = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_INODE:
+ l->ino = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_MODE:
+ l->mode = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_OP:
+ l->op = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_PATH:
+ s = sqlite3_column_text(h->stmt, column++);
+ if (s)
+ l->path = strdup((char *) s);
+ break;
+ case PSQF_PROGRAM:
+ s = sqlite3_column_text(h->stmt, column++);
+ if (s)
+ l->program = strdup((char *) s);
+ break;
+ case PSQF_RESULT:
+ l->result = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_SEVERITY:
+ l->severity = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_STAMP:
+ l->stamp = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_TAG:
+ s = sqlite3_column_text(h->stmt, column++);
+ if (s)
+ l->tag = strdup((char *) s);
+ break;
+ case PSQF_TEXT:
+ s = sqlite3_column_text(h->stmt, column++);
+ if (s)
+ l->text = strdup((char *) s);
+ break;
+ case PSQF_TYPE:
+ l->type = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_UID:
+ l->uid = sqlite3_column_int64(h->stmt, column++);
+ break;
+ case PSQF_ORDER:
+ case PSQF_FTYPE:
+ case PSQF_PERM:
+ pseudo_diag("field %s should not be in the fields list.\n",
+ pseudo_query_field_name(f));
+ return 0;
+ break;
+ default:
+ pseudo_diag("unknown field %d\n", f);
+ return 0;
+ break;
+ }
+ }
+
+ return l;
+}
+
+void
+pdb_history_free(log_history h) {
+ if (!h)
+ return;
+ if (h->stmt) {
+ sqlite3_reset(h->stmt);
+ sqlite3_finalize(h->stmt);
+ }
+ free(h);
+}
+
+void
+log_entry_free(log_entry *e) {
+ if (!e)
+ return;
+ free(e->text);
+ free(e->path);
+ free(e->program);
+ free(e->tag);
+ free(e);
+}
+
+/* Now for the actual file handling code! */
+
+/* pdb_link_file: Creates a new file from msg, using the provided path
+ * or 'NAMELESS FILE'.
+ */
+int
+pdb_link_file(pseudo_msg_t *msg, long long *row) {
+ static sqlite3_stmt *insert;
+ int rc;
+ char *sql = "INSERT INTO files "
+ " ( path, dev, ino, uid, gid, mode, rdev, deleting ) "
+ " VALUES (?, ?, ?, ?, ?, ?, ?, 0);";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!insert) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &insert, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare INSERT statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (msg->pathlen) {
+ sqlite3_bind_text(insert, 1, msg->path, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_text(insert, 1, "NAMELESS FILE", -1, SQLITE_STATIC);
+ }
+ sqlite3_bind_int(insert, 2, msg->dev);
+ sqlite3_bind_int64(insert, 3, msg->ino);
+ sqlite3_bind_int(insert, 4, msg->uid);
+ sqlite3_bind_int(insert, 5, msg->gid);
+ sqlite3_bind_int(insert, 6, msg->mode);
+ sqlite3_bind_int(insert, 7, msg->rdev);
+ pseudo_debug(PDBGF_DB | PDBGF_FILE, "linking %s: dev %llu, ino %llu, mode %o, owner %d\n",
+ (msg->pathlen ? msg->path : "<nil> (as NAMELESS FILE)"),
+ (unsigned long long) msg->dev, (unsigned long long) msg->ino,
+ (int) msg->mode, msg->uid);
+ file_db_dirty = 1;
+ rc = sqlite3_step(insert);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "insert may have failed (rc %d)", rc);
+ }
+ /* some users care what the row ID is */
+ if (row) {
+ *row = sqlite3_last_insert_rowid(file_db);
+ }
+ sqlite3_reset(insert);
+ sqlite3_clear_bindings(insert);
+ return rc != SQLITE_DONE;
+}
+
+/* pdb_unlink_file_dev: Delete every instance of a dev/inode pair. */
+int
+pdb_unlink_file_dev(pseudo_msg_t *msg) {
+ static sqlite3_stmt *sql_delete;
+ int rc;
+ char *sql = "DELETE FROM files WHERE dev = ? AND ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!sql_delete) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &sql_delete, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ sqlite3_bind_int(sql_delete, 1, msg->dev);
+ sqlite3_bind_int64(sql_delete, 2, msg->ino);
+ file_db_dirty = 1;
+ rc = sqlite3_step(sql_delete);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "delete by inode may have failed");
+ }
+ sqlite3_reset(sql_delete);
+ sqlite3_clear_bindings(sql_delete);
+ return rc != SQLITE_DONE;
+}
+
+/* provide a path for a 'NAMELESS FILE' entry */
+int
+pdb_update_file_path(pseudo_msg_t *msg) {
+ static sqlite3_stmt *update;
+ int rc;
+ char *sql = "UPDATE files SET path = ? "
+ "WHERE path = 'NAMELESS FILE' and dev = ? AND ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!update) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ if (!msg || !msg->pathlen) {
+ pseudo_debug(PDBGF_DB, "can't update a file without a message or path.\n");
+ return 1;
+ }
+ sqlite3_bind_text(update, 1, msg->path, -1, SQLITE_STATIC);
+ sqlite3_bind_int(update, 2, msg->dev);
+ sqlite3_bind_int64(update, 3, msg->ino);
+ file_db_dirty = 1;
+ rc = sqlite3_step(update);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update path by inode may have failed");
+ }
+ sqlite3_reset(update);
+ sqlite3_clear_bindings(update);
+ return rc != SQLITE_DONE;
+}
+
+/* mark a file for pending deletion by a given client */
+int
+pdb_may_unlink_file(pseudo_msg_t *msg, int deleting) {
+ static sqlite3_stmt *mark_file;
+ int rc, exact;
+ char *sql_mark_file = "UPDATE files SET deleting = ? WHERE path = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!mark_file) {
+ rc = sqlite3_prepare_v2(file_db, sql_mark_file, strlen(sql_mark_file), &mark_file, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (msg->pathlen) {
+ sqlite3_bind_int(mark_file, 1, deleting);
+ sqlite3_bind_text(mark_file, 2, msg->path, -1, SQLITE_STATIC);
+ } else {
+ pseudo_debug(PDBGF_DB, "cannot mark a file for pending deletion without a path.");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(mark_file);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "mark for deletion may have failed");
+ return 1;
+ }
+ exact = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(exact %d) ", exact);
+ sqlite3_reset(mark_file);
+ sqlite3_clear_bindings(mark_file);
+ /* indicate whether we marked something */
+ if (exact > 0)
+ return 0;
+ else
+ return 1;
+}
+
+/* unmark a file for pending deletion */
+int
+pdb_cancel_unlink_file(pseudo_msg_t *msg) {
+ static sqlite3_stmt *mark_file;
+ int rc, exact;
+ char *sql_mark_file = "UPDATE files SET deleting = 0 WHERE path = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!mark_file) {
+ rc = sqlite3_prepare_v2(file_db, sql_mark_file, strlen(sql_mark_file), &mark_file, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (msg->pathlen) {
+ sqlite3_bind_text(mark_file, 1, msg->path, -1, SQLITE_STATIC);
+ } else {
+ pseudo_debug(PDBGF_DB, "cannot unmark a file for pending deletion without a path.");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(mark_file);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "unmark for deletion may have failed");
+ }
+ exact = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(exact %d) ", exact);
+ sqlite3_reset(mark_file);
+ sqlite3_clear_bindings(mark_file);
+ return rc != SQLITE_DONE;
+}
+
+/* delete all files attached to a given cookie;
+ * used for database fixup passes.
+ */
+int
+pdb_did_unlink_files(int deleting) {
+ static sqlite3_stmt *delete_exact;
+ int rc, exact;
+ char *sql_delete_exact = "DELETE FROM files WHERE deleting = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!delete_exact) {
+ rc = sqlite3_prepare_v2(file_db, sql_delete_exact, strlen(sql_delete_exact), &delete_exact, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (deleting == 0) {
+ pseudo_diag("did_unlink_files: deleting must be non-zero.\n");
+ return 0;
+ }
+ sqlite3_bind_int(delete_exact, 1, deleting);
+ file_db_dirty = 1;
+ rc = sqlite3_step(delete_exact);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "cleanup of files marked for deletion may have failed");
+ }
+ exact = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(exact %d)\n", exact);
+ sqlite3_reset(delete_exact);
+ sqlite3_clear_bindings(delete_exact);
+ return rc != SQLITE_DONE;
+}
+
+/* confirm deletion of a specific file by a given client */
+int
+pdb_did_unlink_file(char *path, int deleting) {
+ static sqlite3_stmt *delete_exact;
+ int rc, exact;
+ char *sql_delete_exact = "DELETE FROM files WHERE path = ? AND deleting = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!delete_exact) {
+ rc = sqlite3_prepare_v2(file_db, sql_delete_exact, strlen(sql_delete_exact), &delete_exact, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!path) {
+ pseudo_debug(PDBGF_DB, "cannot unlink a file without a path.");
+ return 1;
+ }
+ sqlite3_bind_text(delete_exact, 1, path, -1, SQLITE_STATIC);
+ sqlite3_bind_int(delete_exact, 2, deleting);
+ file_db_dirty = 1;
+ rc = sqlite3_step(delete_exact);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "cleanup of file marked for deletion may have failed");
+ }
+ exact = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(exact %d)\n", exact);
+ sqlite3_reset(delete_exact);
+ sqlite3_clear_bindings(delete_exact);
+ return rc != SQLITE_DONE;
+}
+
+/* unlink a file, by path */
+int
+pdb_unlink_file(pseudo_msg_t *msg) {
+ static sqlite3_stmt *delete_exact;
+ int rc, exact;
+ char *sql_delete_exact = "DELETE FROM files WHERE path = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!delete_exact) {
+ rc = sqlite3_prepare_v2(file_db, sql_delete_exact, strlen(sql_delete_exact), &delete_exact, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (msg->pathlen) {
+ sqlite3_bind_text(delete_exact, 1, msg->path, -1, SQLITE_STATIC);
+ } else {
+ pseudo_debug(PDBGF_DB, "cannot unlink a file without a path.");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(delete_exact);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "delete exact by path may have failed");
+ }
+ exact = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(exact %d) ", exact);
+ sqlite3_reset(delete_exact);
+ sqlite3_clear_bindings(delete_exact);
+ return rc != SQLITE_DONE;
+}
+
+/* Unlink all the contents of directory
+ * SQLite performance limitations:
+ * path LIKE foo '/%' -> can't use index
+ * path = A OR path = B -> can't use index
+ * Solution:
+ * 1. From web http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html
+ * Use > and < instead of a glob at the end.
+ */
+int
+pdb_unlink_contents(pseudo_msg_t *msg) {
+ static sqlite3_stmt *delete_sub;
+ int rc, sub;
+ char *sql_delete_sub = "DELETE FROM files WHERE "
+ "(path > (? || '/') AND path < (? || '0'));";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!delete_sub) {
+ rc = sqlite3_prepare_v2(file_db, sql_delete_sub, strlen(sql_delete_sub), &delete_sub, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (msg->pathlen) {
+ sqlite3_bind_text(delete_sub, 1, msg->path, -1, SQLITE_STATIC);
+ sqlite3_bind_text(delete_sub, 2, msg->path, -1, SQLITE_STATIC);
+ } else {
+ pseudo_debug(PDBGF_DB, "cannot unlink a file without a path.");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(delete_sub);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "delete sub by path may have failed");
+ }
+ sub = sqlite3_changes(file_db);
+ pseudo_debug(PDBGF_DB, "(sub %d) ", sub);
+ sqlite3_reset(delete_sub);
+ sqlite3_clear_bindings(delete_sub);
+ return rc != SQLITE_DONE;
+}
+
+/* rename a file.
+ * If there are any other files with paths that are rooted in "file", then
+ * file must really be a directory, and they should be renamed.
+ *
+ * This is tricky:
+ * You have to rename everything starting with "path/", but also "path" itself
+ * with no slash. Luckily for us, SQL can replace the old path with the
+ * new path.
+ */
+int
+pdb_rename_file(const char *oldpath, pseudo_msg_t *msg) {
+ static sqlite3_stmt *update_exact, *update_sub;
+ int rc;
+ char *sql_update_exact = "UPDATE files SET path = ? WHERE path = ?;";
+ char *sql_update_sub = "UPDATE files SET path = replace(path, ?, ?) "
+ "WHERE (path > (? || '/') AND path < (? || '0'));";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!update_exact) {
+ rc = sqlite3_prepare_v2(file_db, sql_update_exact, strlen(sql_update_exact), &update_exact, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ if (!update_sub) {
+ rc = sqlite3_prepare_v2(file_db, sql_update_sub, strlen(sql_update_sub), &update_sub, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (!msg->pathlen) {
+ pseudo_debug(PDBGF_DB, "rename: No path provided (ino %llu)\n", (unsigned long long) msg->ino);
+ return 1;
+ }
+ if (!oldpath) {
+ pseudo_debug(PDBGF_DB, "rename: No old path for %s\n", msg->path);
+ return 1;
+ }
+ pseudo_debug(PDBGF_DB, "rename: Changing %s to %s\n", oldpath, msg->path);
+ rc = sqlite3_bind_text(update_exact, 1, msg->path, -1, SQLITE_STATIC);
+ rc = sqlite3_bind_text(update_exact, 2, oldpath, -1, SQLITE_STATIC);
+ rc = sqlite3_bind_text(update_sub, 1, oldpath, -1, SQLITE_STATIC);
+ rc = sqlite3_bind_text(update_sub, 2, msg->path, -1, SQLITE_STATIC);
+ rc = sqlite3_bind_text(update_sub, 3, oldpath, -1, SQLITE_STATIC);
+ rc = sqlite3_bind_text(update_sub, 4, oldpath, -1, SQLITE_STATIC);
+
+ rc = sqlite3_exec(file_db, "BEGIN;", NULL, NULL, NULL);
+
+ file_db_dirty = 1;
+ rc = sqlite3_step(update_exact);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update exact may have failed: rc %d", rc);
+ }
+ rc = sqlite3_step(update_sub);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update sub may have failed: rc %d", rc);
+ }
+ sqlite3_reset(update_exact);
+ sqlite3_reset(update_sub);
+
+ rc = sqlite3_exec(file_db, "END;", NULL, NULL, NULL);
+
+ sqlite3_clear_bindings(update_exact);
+ sqlite3_clear_bindings(update_sub);
+ return rc != SQLITE_DONE;
+}
+
+/* renumber device only.
+ * this is used if the filesystem moves to a new device, without changing
+ * inode allocations.
+ */
+int
+pdb_renumber_all(dev_t from, dev_t to) {
+ static sqlite3_stmt *update;
+ int rc;
+ char *sql = "UPDATE files "
+ " SET dev = ? "
+ " WHERE dev = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!update) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ rc = sqlite3_bind_int(update, 1, to);
+ if (rc) {
+ dberr(file_db, "error binding device numbers to update");
+ }
+ rc = sqlite3_bind_int(update, 2, from);
+ if (rc) {
+ dberr(file_db, "error binding device numbers to update");
+ }
+
+ file_db_dirty = 1;
+ rc = sqlite3_step(update);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update may have failed: rc %d", rc);
+ }
+ sqlite3_reset(update);
+ sqlite3_clear_bindings(update);
+ pseudo_debug(PDBGF_DB, "updating device dev %llu to %llu\n",
+ (unsigned long long) from, (unsigned long long) to);
+ return rc != SQLITE_DONE;
+}
+
+/* change dev/inode for a given path -- used only by RENAME for now.
+ */
+int
+pdb_update_inode(pseudo_msg_t *msg) {
+ static sqlite3_stmt *update;
+ int rc;
+ char *sql = "UPDATE files "
+ " SET dev = ?, ino = ? "
+ " WHERE path = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!update) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (!msg->pathlen) {
+ pseudo_diag("Can't update the inode of a file without its path.\n");
+ return 1;
+ }
+ sqlite3_bind_int(update, 1, msg->dev);
+ sqlite3_bind_int64(update, 2, msg->ino);
+ rc = sqlite3_bind_text(update, 3, msg->path, -1, SQLITE_STATIC);
+ if (rc) {
+ /* msg->path can never be null, and if msg didn't
+ * have a non-zero pathlen, we'd already have exited
+ * above
+ */
+ dberr(file_db, "error binding %s to select", msg->path);
+ }
+
+ file_db_dirty = 1;
+ rc = sqlite3_step(update);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update may have failed: rc %d", rc);
+ }
+ sqlite3_reset(update);
+ sqlite3_clear_bindings(update);
+ pseudo_debug(PDBGF_DB, "updating path %s to dev %llu, ino %llu\n",
+ msg->path,
+ (unsigned long long) msg->dev, (unsigned long long) msg->ino);
+ return rc != SQLITE_DONE;
+}
+
+/* change uid/gid/mode/rdev in any existing entries matching a given
+ * dev/inode pair.
+ */
+int
+pdb_update_file(pseudo_msg_t *msg) {
+ static sqlite3_stmt *update;
+ int rc;
+ char *sql = "UPDATE files "
+ " SET uid = ?, gid = ?, mode = ?, rdev = ? "
+ " WHERE dev = ? AND ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!update) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &update, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ sqlite3_bind_int(update, 1, msg->uid);
+ sqlite3_bind_int(update, 2, msg->gid);
+ sqlite3_bind_int(update, 3, msg->mode);
+ sqlite3_bind_int(update, 4, msg->rdev);
+ sqlite3_bind_int(update, 5, msg->dev);
+ sqlite3_bind_int64(update, 6, msg->ino);
+
+ file_db_dirty = 1;
+ rc = sqlite3_step(update);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update may have failed: rc %d", rc);
+ }
+ sqlite3_reset(update);
+ sqlite3_clear_bindings(update);
+ pseudo_debug(PDBGF_DB, "updating dev %llu, ino %llu, new mode %o, owner %d\n",
+ (unsigned long long) msg->dev, (unsigned long long) msg->ino,
+ (int) msg->mode, msg->uid);
+ return rc != SQLITE_DONE;
+}
+
+/* find file using both path AND dev/inode as key */
+int
+pdb_find_file_exact(pseudo_msg_t *msg, long long *row) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE path = ? AND dev = ? AND ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ rc = sqlite3_bind_text(select, 1, msg->path, -1, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "error binding %s to select", msg->pathlen ? msg->path : "<nil>");
+ }
+ sqlite3_bind_int(select, 2, msg->dev);
+ sqlite3_bind_int64(select, 3, msg->ino);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ if (row) {
+ *row = sqlite3_column_int64(select, 0);
+ }
+ msg->uid = (unsigned long) sqlite3_column_int64(select, 4);
+ msg->gid = (unsigned long) sqlite3_column_int64(select, 5);
+ msg->mode = (unsigned long) sqlite3_column_int64(select, 6);
+ msg->rdev = (unsigned long) sqlite3_column_int64(select, 7);
+ msg->deleting = (int) sqlite3_column_int64(select, 8);
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_exact: sqlite_done on first row\n");
+ rc = 1;
+ break;
+ default:
+ dberr(file_db, "find_exact: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc;
+}
+
+/* find file using path as a key */
+int
+pdb_find_file_path(pseudo_msg_t *msg, long long *row) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE path = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 1;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ if (!msg->pathlen) {
+ return 1;
+ }
+ rc = sqlite3_bind_text(select, 1, msg->path, -1, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "error binding %s to select", msg->pathlen ? msg->path : "<nil>");
+ }
+
+ rc = sqlite3_column_count(select);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ if (row) {
+ *row = sqlite3_column_int64(select, 0);
+ }
+ msg->dev = sqlite3_column_int64(select, 2);
+ msg->ino = sqlite3_column_int64(select, 3);
+ msg->uid = sqlite3_column_int64(select, 4);
+ msg->gid = sqlite3_column_int64(select, 5);
+ msg->mode = sqlite3_column_int64(select, 6);
+ msg->rdev = sqlite3_column_int64(select, 7);
+ msg->deleting = (int) sqlite3_column_int64(select, 8);
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_path: sqlite_done on first row\n");
+ rc = 1;
+ break;
+ default:
+ dberr(file_db, "find_path: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc;
+}
+
+/* find path for a file, given dev and inode as keys */
+char *
+pdb_get_file_path(pseudo_msg_t *msg) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT path FROM files WHERE dev = ? AND ino = ?;";
+ char *response;
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 0;
+ }
+ }
+ if (!msg) {
+ return 0;
+ }
+ sqlite3_bind_int(select, 1, msg->dev);
+ sqlite3_bind_int64(select, 2, msg->ino);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ response = (char *) sqlite3_column_text(select, 0);
+ if (response) {
+ if (strcmp(response, "NAMELESS FILE")) {
+ response = strdup(response);
+ } else {
+ response = 0;
+ }
+ }
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_dev: sqlite_done on first row\n");
+ response = 0;
+ break;
+ default:
+ dberr(file_db, "find_dev: select returned neither a row nor done");
+ response = 0;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return response;
+}
+
+/* find file using dev/inode as key */
+int
+pdb_find_file_dev(pseudo_msg_t *msg, long long *row, char **path) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE dev = ? AND ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ sqlite3_bind_int(select, 1, msg->dev);
+ sqlite3_bind_int64(select, 2, msg->ino);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ if (row) {
+ *row = sqlite3_column_int64(select, 0);
+ }
+ msg->uid = (unsigned long) sqlite3_column_int64(select, 4);
+ msg->gid = (unsigned long) sqlite3_column_int64(select, 5);
+ msg->mode = (unsigned long) sqlite3_column_int64(select, 6);
+ msg->rdev = (unsigned long) sqlite3_column_int64(select, 7);
+ msg->deleting = (int) sqlite3_column_int64(select, 8);
+ /* stash path */
+ if (path) {
+ *path = strdup((char *) sqlite3_column_text(select, 1));
+ pseudo_debug(PDBGF_FILE, "find_file_dev: path %s\n",
+ *path ? *path : "<nil>");
+ }
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_dev: sqlite_done on first row\n");
+ rc = 1;
+ break;
+ default:
+ dberr(file_db, "find_dev: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc;
+}
+
+int
+pdb_get_xattr(long long file_id, char **value, size_t *len) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *response;
+ size_t length;
+ char *sql = "SELECT value FROM xattrs WHERE file_id = ? AND name = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ pseudo_debug(PDBGF_XATTR, "requested xattr named '%s' for file %lld\n", *value, file_id);
+ sqlite3_bind_int(select, 1, file_id);
+ rc = sqlite3_bind_text(select, 2, *value, -1, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr name to SELECT.");
+ return 1;
+ }
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ response = (char *) sqlite3_column_text(select, 0);
+ length = sqlite3_column_bytes(select, 0);
+ pseudo_debug(PDBGF_XATTR, "got %d-byte results: '%s'\n",
+ (int) length, response);
+ if (response && length >= 1) {
+ /* not a strdup because the values can contain
+ * arbitrary bytes.
+ */
+ *value = malloc(length);
+ memcpy(*value, response, length);
+ *len = length;
+ rc = 0;
+ } else {
+ *value = NULL;
+ *len = 0;
+ rc = 1;
+ }
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_exact: sqlite_done on first row\n");
+ rc = 1;
+ break;
+ default:
+ dberr(file_db, "find_exact: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc;
+}
+
+int
+pdb_list_xattr(long long file_id, char **value, size_t *len) {
+ static sqlite3_stmt *select;
+ size_t allocated = 0;
+ size_t used = 0;
+ char *buffer = 0;
+ int rc;
+ char *sql = "SELECT name FROM xattrs WHERE file_id = ?;";
+
+ /* if we don't have a record of the file, it must not have
+ * any extended attributes...
+ */
+ if (file_id == -1) {
+ *value = NULL;
+ *len = 0;
+ return 0;
+ }
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ sqlite3_bind_int(select, 1, file_id);
+ do {
+ rc = sqlite3_step(select);
+ if (rc == SQLITE_ROW) {
+ char *value = (char *) sqlite3_column_text(select, 0);
+ size_t len = sqlite3_column_bytes(select, 0);
+ if (!buffer) {
+ allocated = round_up(len, 256);
+ buffer = malloc(allocated);
+ }
+ if (used + len + 2 > allocated) {
+ size_t new_allocated = round_up(used + len + 2, 256);
+ char *new_buffer = malloc(new_allocated);
+ memcpy(new_buffer, buffer, used);
+ free(buffer);
+ allocated = new_allocated;
+ buffer = new_buffer;
+ }
+ memcpy(buffer + used, value, len);
+ buffer[used + len] = '\0';
+ used = used + len + 1;
+ } else if (rc == SQLITE_DONE) {
+ *value = buffer;
+ *len = used;
+ } else {
+ dberr(file_db, "non-row response from select?");
+ free(buffer);
+ *value = NULL;
+ *len = 0;
+ }
+ } while (rc == SQLITE_ROW);
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc != SQLITE_DONE;
+}
+
+int
+pdb_remove_xattr(long long file_id, char *value, size_t len) {
+ static sqlite3_stmt *delete;
+ int rc;
+ char *sql = "DELETE FROM xattrs WHERE file_id = ? AND name = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!delete) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &delete, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare DELETE statement");
+ return 1;
+ }
+ }
+ sqlite3_bind_int(delete, 1, file_id);
+ rc = sqlite3_bind_text(delete, 2, value, len, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr name to DELETE.");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(delete);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "delete xattr may have failed");
+ }
+ sqlite3_reset(delete);
+ sqlite3_clear_bindings(delete);
+ return rc != SQLITE_DONE;
+}
+
+int
+pdb_set_xattr(long long file_id, char *value, size_t len, int flags) {
+ static sqlite3_stmt *select, *update, *insert;
+ int rc;
+ long long existing_row = -1;
+ char *select_sql = "SELECT id FROM xattrs WHERE file_id = ? AND name = ?;";
+ char *insert_sql = "INSERT INTO xattrs "
+ " ( file_id, name, value ) "
+ " VALUES (?, ?, ?);";
+ char *update_sql = "UPDATE xattrs SET value = ? WHERE id = ?;";
+ char *vname = value;
+ size_t vlen;
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, select_sql, strlen(select_sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ sqlite3_bind_int(select, 1, file_id);
+ rc = sqlite3_bind_text(select, 2, value, -1, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr name to SELECT.");
+ return 1;
+ }
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ existing_row = sqlite3_column_int64(select, 0);
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB | PDBGF_VERBOSE, "find_exact: sqlite_done on first row\n");
+ existing_row = -1;
+ break;
+ default:
+ dberr(file_db, "set_xattr: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ if (flags == XATTR_CREATE && existing_row != -1) {
+ pseudo_debug(PDBGF_DB, "XATTR_CREATE with an existing row, failing.");
+ return 1;
+ }
+ if (flags == XATTR_REPLACE && existing_row == -1) {
+ pseudo_debug(PDBGF_DB, "XATTR_REPLACE with no existing row, failing.");
+ return 1;
+ }
+ /* the material after the name is the value */
+ vlen = strlen(value);
+ len = len - (vlen + 1);
+ value = value + vlen + 1;
+ pseudo_debug(PDBGF_XATTR, "trying to set a value for %lld: name is '%s' [%d/%d bytes], value is '%s' [%d bytes]. Existing row %lld.\n",
+ file_id, vname, (int) vlen, (int) (len + vlen + 1), value, (int) len, existing_row);
+ if (existing_row != -1) {
+ /* update */
+ if (!update) {
+ rc = sqlite3_prepare_v2(file_db, update_sql, strlen(update_sql), &update, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare UPDATE statement");
+ return 1;
+ }
+ }
+ rc = sqlite3_bind_text(update, 1, value, len, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr value to UPDATE.");
+ return 1;
+ }
+ sqlite3_bind_int(update, 2, existing_row);
+ file_db_dirty = 1;
+ rc = sqlite3_step(update);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "update xattr may have failed");
+ }
+ sqlite3_reset(update);
+ sqlite3_clear_bindings(update);
+ return rc != SQLITE_DONE;
+ } else {
+ /* insert */
+ if (!insert) {
+ rc = sqlite3_prepare_v2(file_db, insert_sql, strlen(insert_sql), &insert, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare INSERT statement");
+ return 1;
+ }
+ }
+ pseudo_debug(PDBGF_XATTR, "insert should be getting ID %lld\n", file_id);
+ sqlite3_bind_int64(insert, 1, file_id);
+ rc = sqlite3_bind_text(insert, 2, vname, -1, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr name to INSERT statement");
+ return 1;
+ }
+ rc = sqlite3_bind_text(insert, 3, value, len, SQLITE_STATIC);
+ if (rc) {
+ dberr(file_db, "couldn't bind xattr value to INSERT statement");
+ return 1;
+ }
+ file_db_dirty = 1;
+ rc = sqlite3_step(insert);
+ if (rc != SQLITE_DONE) {
+ dberr(file_db, "insert xattr may have failed");
+ }
+ sqlite3_reset(insert);
+ sqlite3_clear_bindings(insert);
+ return rc != SQLITE_DONE;
+ }
+ return rc;
+}
+
+/* find file using only inode as key. Unused for now, planned to come
+ * in for NFS usage.
+ */
+int
+pdb_find_file_ino(pseudo_msg_t *msg, long long *row) {
+ static sqlite3_stmt *select;
+ int rc;
+ char *sql = "SELECT * FROM files WHERE ino = ?;";
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+ if (!select) {
+ rc = sqlite3_prepare_v2(file_db, sql, strlen(sql), &select, NULL);
+ if (rc) {
+ dberr(file_db, "couldn't prepare SELECT statement");
+ return 1;
+ }
+ }
+ if (!msg) {
+ return 1;
+ }
+ sqlite3_bind_int64(select, 1, msg->ino);
+ rc = sqlite3_step(select);
+ switch (rc) {
+ case SQLITE_ROW:
+ if (row) {
+ *row = sqlite3_column_int64(select, 0);
+ }
+ msg->dev = (unsigned long) sqlite3_column_int64(select, 2);
+ msg->uid = (unsigned long) sqlite3_column_int64(select, 4);
+ msg->gid = (unsigned long) sqlite3_column_int64(select, 5);
+ msg->mode = (unsigned long) sqlite3_column_int64(select, 6);
+ msg->rdev = (unsigned long) sqlite3_column_int64(select, 7);
+ msg->deleting = (int) sqlite3_column_int64(select, 8);
+ rc = 0;
+ break;
+ case SQLITE_DONE:
+ pseudo_debug(PDBGF_DB, "find_ino: sqlite_done on first row\n");
+ rc = 1;
+ break;
+ default:
+ dberr(file_db, "find_ino: select returned neither a row nor done");
+ rc = 1;
+ break;
+ }
+ sqlite3_reset(select);
+ sqlite3_clear_bindings(select);
+ return rc;
+}
+
+pdb_file_list
+pdb_files(void) {
+ pdb_file_list l;
+
+ if (!file_db && get_dbs()) {
+ pseudo_diag("%s: database error.\n", __func__);
+ return 0;
+ }
+
+ l = malloc(sizeof(*l));
+ if (!l)
+ return NULL;
+
+ l->rc = sqlite3_prepare_v2(file_db, "SELECT path, dev, ino, uid, gid, mode, rdev FROM files", -1, &l->stmt, NULL);
+ if (l->rc) {
+ dberr(file_db, "Couldn't start SELECT from files.\n");
+ free(l);
+ return NULL;
+ }
+ return l;
+}
+
+pseudo_msg_t *
+pdb_file(pdb_file_list l) {
+ const unsigned char *s;
+ pseudo_msg_t *m;
+ int column = 0;
+
+ if (!l || !l->stmt)
+ return 0;
+ /* in case someone tries again after we're already done */
+ if (l->rc == SQLITE_DONE) {
+ return 0;
+ }
+ l->rc = sqlite3_step(l->stmt);
+ if (l->rc == SQLITE_DONE) {
+ return 0;
+ } else if (l->rc != SQLITE_ROW) {
+ dberr(log_db, "statement failed");
+ return 0;
+ }
+ s = sqlite3_column_text(l->stmt, column++);
+ m = pseudo_msg_new(0, (const char *) s);
+ if (!m) {
+ pseudo_diag("couldn't allocate file message.\n");
+ return NULL;
+ }
+ pseudo_debug(PDBGF_DB, "pdb_file: '%s'\n", s ? (const char *) s : "<nil>");
+ m->dev = sqlite3_column_int64(l->stmt, column++);
+ m->ino = sqlite3_column_int64(l->stmt, column++);
+ m->uid = sqlite3_column_int64(l->stmt, column++);
+ m->gid = sqlite3_column_int64(l->stmt, column++);
+ m->mode = sqlite3_column_int64(l->stmt, column++);
+ m->rdev = sqlite3_column_int64(l->stmt, column++);
+ return m;
+}
+
+void
+pdb_files_done(pdb_file_list l) {
+ if (!l)
+ return;
+ if (l->stmt) {
+ sqlite3_reset(l->stmt);
+ sqlite3_finalize(l->stmt);
+ }
+ free(l);
+}