/*
* pseudo_db.c, sqlite3 interface
*
* Copyright (c) 2008-2010,2013 Wind River Systems, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-only
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <time.h>
#include <sys/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;
/* So the migrations were getting to be large and complicated. Phase
* one of getting rid of that is to merge their effects in and set up
* a dummy migration to indicate that this was done.
*/
static id_row files_default_migrations[] = {
{ "%d, -1, 'N/A (initial setup)'", 8 },
{ NULL, 0 },
};
static id_row logs_default_migrations[] = {
{ "%d, -1, 'N/A (initial setup)'", 7 },
{ NULL, 0 },
};
/* 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, "
"deleting INTEGER",
NULL,
NULL },
{ "xattrs",
"id INTEGER PRIMARY KEY, "
"dev INTEGER, "
"ino INTEGER, "
"name VARCHAR, "
"value VARCHAR",
NULL,
NULL },
{ "migrations",
"id INTEGER PRIMARY KEY, "
"version INTEGER, "
"stamp INTEGER, "
"sql VARCHAR",
"version, stamp, sql",
files_default_migrations },
{ 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, "
"tag VARCHAR, "
"uid INTEGER, "
"gid INTEGER, "
"access INTEGER, "
"program VARCHAR, "
"type INTEGER"
,
NULL,
NULL },
{ "migrations",
"id INTEGER PRIMARY KEY, "
"version INTEGER, "
"stamp INTEGER, "
"sql VARCHAR",
"version, stamp, sql",
logs_default_migrations },
{ NULL, NULL, NULL, NULL },
};
/* similarly, this creates indexes generically. */
static struct sql_index {
char *name;
char *table;
char *keys;
} file_indexes[] = {
{ "files__path_dev_ino", "files", "path, dev, ino" },
{ "files__dev_ino", "files", "dev, ino" },
{ "migration__version", "migrations", "version" },
{ "xattrs_dev_ino", "xattrs", "dev, ino" },
{ "xattrs_dev_ino_name", "xattrs", "dev, ino, name" },
{ NULL, NULL, NULL },
}, log_indexes[] = {
{ "migration__version", "migrations", "version" },
{ 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;" },
/* oops. I made a design mistake, and the table is in the wrong
* format. But sqlite doesn't support modifying constraints on an
* existing table, or dropping columns. Soooooo.
*
* Also, you can't have a foreign key relationship to data which
* aren't actually unique, but the entire problem is that dev/ino
* are not unique.
*
* Note that the name entry is not really all that useful; it's mostly
* there to help with debugging, now.
*/
{
"CREATE TABLE xattrs_new ("
"id INTEGER PRIMARY KEY, "
"dev INTEGER, "
"ino INTEGER, "
"name VARCHAR, "
"value VARCHAR"
");",
},
{
"INSERT INTO xattrs_new (dev, ino, name, value) "
"SELECT "
"(SELECT dev FROM files WHERE id = file_id), "
"(SELECT ino FROM files WHERE id = file_id), "
"name, "
"value "
"FROM xattrs;"
},
{
"DROP TABLE xattrs;"
},
{
"ALTER TABLE xattrs_new RENAME TO xattrs;",
},
{
"CREATE INDEX xattrs__dev_ino ON xattrs (dev, ino);",
},
{
"CREATE UNIQUE INDEX xattrs__dev_ino_name ON xattrs (dev, ino, name);",
},
{ 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);
}
}
/* sqlite3 supports only signed integers, thus, the highest positive
* value is 2^63-1. some filesystems with 64-bit ino_t will produce
* ino_t values in the 2^63..2^64-1 range. we convert those into
* the value you'd get treating the 2^63 bit as a sign bit, so sqlite
* doesn't convert them into floats, because the float type can't
* represent values accurately in this range, which can result in
* erroneous matches, etcetera.
*
* note, when reading the values back out, the conversion does the
* right thing. e.g., if the inode number had been 2^63 exactly,
* it will convert to -(2^63). that, converted to unsigned, will
* convert back to 2^63 exactly, etcetera.
*
* so far as i know, ino_t is always unsigned. we don't care about
* the 32-bit inode case; sqlite can represent those just fine.
*/
static int64_t
signed_ino(ino_t ino) {
if (ino > (uint64_t) INT64_MAX) {
return (int64_t) (ino + INT64_MIN) + INT64_MIN;
}
return (int64_t) ino;
}
#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);
pseudo_debug(PDBGF_DB, "data setup: %s\n", sql);
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;
}
}
}
/* the migrations table is assumed to exist, because we now
* create it automatically; in a previous implementation, it was
* only created by the first migration.
*/
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;
}
for (m = sql_migrations; m->sql; ++m)
;
/* m points to the null, so available migrations is one lower */
available_migrations = m - sql_migrations - 1;
/* I am pretty sure this can never happen. */
if (version < -1)
version = -1;
/* I hope this can never happen. */
pseudo_debug(PDBGF_DB, "version %d, available_migrations %d\n",
version, available_migrations);
if (version > available_migrations)
version = available_migrations;
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) {
char datebuf[64];
struct tm stamp_tm;
struct timeval stamp;
gettimeofday(&stamp, NULL);
localtime_r(&stamp.tv_sec, &stamp_tm);
strftime(datebuf, 64, "%H:%M:%S", &stamp_tm);
pseudo_diag("db cleanup for server shutdown, %s.%03d\n",
datebuf, (int) (stamp.tv_usec / 1000));
#ifdef USE_MEMORY_DB
if (real_file_db) {
pdb_backup();
sqlite3_close(real_file_db);
}
gettimeofday(&stamp, NULL);
localtime_r(&stamp.tv_sec, &stamp_tm);
strftime(datebuf, 64, "%H:%M:%S", &stamp_tm);
pseudo_diag("memory-to-file backup complete, %s.%03d.\n",
datebuf, (int) (stamp.tv_usec / 1000));
#endif
if (file_db)
sqlite3_close(file_db);
if (log_db)
sqlite3_close(log_db);
gettimeofday(&stamp, NULL);
localtime_r(&stamp.tv_sec, &stamp_tm);
strftime(datebuf, 64, "%H:%M:%S", &stamp_tm);
pseudo_diag("db cleanup finished, %s.%03d\n",
datebuf, (int) (stamp.tv_usec / 1000));
}
/* 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;
pseudo_debug(PDBGF_DB, "get_db(%s)\n", dbinfo->pathname);
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 = (ino_t) 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++, signed_ino(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++, signed_ino(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 = (ino_t) 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! */
/* nuke any unlinked xattrs. Only used from the rare case where we try
* to nuke a whole bunch of "deleting" files.
*/
static void
pdb_clear_unused_xattrs(void) {
static sqlite3_stmt *delete;
char *delete_sql = "DELETE FROM xattrs WHERE (SELECT COUNT(*) FROM files WHERE dev = xattrs.dev AND ino = xattrs.ino) = 0;";
int rc;
if (!file_db && get_dbs()) {
pseudo_diag("%s: database error.\n", __func__);
return;
}
if (!delete) {
rc = sqlite3_prepare_v2(file_db, delete_sql, strlen(delete_sql), &delete, NULL);
if (rc) {
dberr(file_db, "couldn't prepare DELETE statement for unused xattrs");
return;
}
}
rc = sqlite3_step(delete);
if (rc != SQLITE_DONE) {
dberr(file_db, "delete of unused xattrs may have failed");
}
sqlite3_reset(delete);
}
/* we want to delete extended attributes for a device/inode if there
* are no longer any links with it. Anything that deletes at least one
* link for a given device/inode should then call this.
*/
static void
pdb_clear_xattrs(pseudo_msg_t *msg) {
static sqlite3_stmt *delete;
char *delete_sql = "DELETE FROM xattrs WHERE dev = ? AND ino = ? AND (SELECT COUNT(*) FROM files WHERE dev = ? AND ino = ?) = 0;";
int rc;
if (!msg)
return;
if (!file_db && get_dbs()) {
pseudo_diag("%s: database error.\n", __func__);
return;
}
if (!delete) {
rc = sqlite3_prepare_v2(file_db, delete_sql, strlen(delete_sql), &delete, NULL);
if (rc) {
dberr(file_db, "couldn't prepare DELETE statement for specific inode xattrs");
return;
}
}
sqlite3_bind_int(delete, 1, msg->dev);
sqlite3_bind_int64(delete, 2, signed_ino(msg->ino));
sqlite3_bind_int(delete, 3, msg->dev);
sqlite3_bind_int64(delete, 4, signed_ino(msg->ino));
rc = sqlite3_step(delete);
if (rc != SQLITE_DONE) {
dberr(file_db, "delete of unused xattrs may have failed");
}
sqlite3_reset(delete);
sqlite3_clear_bindings(delete);
}
static void
pdb_copy_xattrs(pseudo_msg_t *oldmsg, pseudo_msg_t *msg) {
static sqlite3_stmt *copy;
char *copy_sql = "INSERT INTO xattrs (dev, ino, name, value) "
"SELECT "
"?, "
"?, "
"name, "
"value "
"FROM xattrs WHERE dev = ? AND ino = ?;";
int rc;
if (!oldmsg || !msg)
return;
if (!file_db && get_dbs()) {
pseudo_diag("%s: database error.\n", __func__);
return;
}
if (!copy) {
rc = sqlite3_prepare_v2(file_db, copy_sql, strlen(copy_sql), ©, NULL);
if (rc) {
dberr(file_db, "couldn't prepare INSERT statement for copying xattrs");
return;
}
}
sqlite3_bind_int(copy, 1, msg->dev);
sqlite3_bind_int64(copy, 2, signed_ino(msg->ino));
sqlite3_bind_int(copy, 3, oldmsg->dev);
sqlite3_bind_int64(copy, 4, signed_ino(oldmsg->ino));
rc = sqlite3_step(copy);
if (rc != SQLITE_DONE) {
dberr(file_db, "copy of xattrs may have failed");
}
sqlite3_reset(copy);
sqlite3_clear_bindings(copy);
}
static void
pdb_check_xattrs(pseudo_msg_t *msg) {
static sqlite3_stmt *scan;
char *scan_sql = "SELECT COUNT(*) FROM files WHERE dev = ? AND ino = ?;";
int rc;
if (!msg)
return;
if (!file_db && get_dbs()) {
pseudo_diag("%s: database error.\n", __func__);
return;
}
if (!scan) {
rc = sqlite3_prepare_v2(file_db, scan_sql, strlen(scan_sql), &scan, NULL);
if (rc) {
dberr(file_db, "couldn't prepare SELECT statement for checking for existing files");
return;
}
}
int existing;
sqlite3_bind_int(scan, 1, msg->dev);
sqlite3_bind_int64(scan, 2, signed_ino(msg->ino));
rc = sqlite3_step(scan);
if (rc == SQLITE_ROW) {
existing = (int) sqlite3_column_int64(scan, 0);
pseudo_debug(PDBGF_DB | PDBGF_VERBOSE,
"scan for existing files: %d\n",
existing);
rc = sqlite3_step(scan);
} else {
pseudo_debug(PDBGF_DB, "scan for existing files: no count?\n");
existing = 0;
}
if (rc != SQLITE_DONE) {
dberr(file_db, "not done after the single row we expected?", rc);
}
sqlite3_reset(scan);
sqlite3_clear_bindings(scan);
/* if there are any matching entries, don't delete xattrs */
if (existing == 0)
pdb_clear_xattrs(msg);
}
/* pdb_link_file: Creates a new file from msg, using the provided path
* or 'NAMELESS FILE'.
*/
int
pdb_link_file(pseudo_msg_t *msg) {
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, signed_ino(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);
}
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, signed_ino(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);
pdb_clear_xattrs(msg);
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);
pdb_check_xattrs(msg);
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);
pdb_clear_unused_xattrs();
return rc != SQLITE_DONE;
}
/* confirm deletion of a specific file by a given client */
int
pdb_did_unlink_file(char *path, pseudo_msg_t *msg, 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);
if (msg) {
pdb_clear_xattrs(msg);
} else {
/* we have to clean everything because we don't know for sure the
* device/inode...
*/
pdb_clear_unused_xattrs();
}
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);
pdb_check_xattrs(msg);
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);
/* we have no idea how many things might have been affected, so. */
pdb_clear_unused_xattrs();
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 *files_update, *xattrs_update;
int rc;
char *files_sql = "UPDATE files "
" SET dev = ? "
" WHERE dev = ?;";
char *xattrs_sql = "UPDATE xattrs "
" SET dev = ? "
" WHERE dev = ?;";
if (!file_db && get_dbs()) {
pseudo_diag("%s: database error.\n", __func__);
return 0;
}
if (!files_update) {
rc = sqlite3_prepare_v2(file_db, files_sql, strlen(files_sql), &files_update, NULL);
if (rc) {
dberr(file_db, "couldn't prepare UPDATE statement");
return 1;
}
}
if (!xattrs_update) {
rc = sqlite3_prepare_v2(file_db, xattrs_sql, strlen(xattrs_sql), &xattrs_update, NULL);
if (rc) {
dberr(file_db, "couldn't prepare UPDATE statement");
return 1;
}
}
rc = sqlite3_bind_int(files_update, 1, to);
if (rc) {
dberr(file_db, "error binding device numbers to update");
}
rc = sqlite3_bind_int(files_update, 2, from);
if (rc) {
dberr(file_db, "error binding device numbers to update");
}
rc = sqlite3_bind_int(xattrs_update, 1, to);
if (rc) {
dberr(file_db, "error binding device numbers to update");
}
rc = sqlite3_bind_int(xattrs_update, 2, from);
if (rc) {
dberr(file_db, "error binding device numbers to update");
}
file_db_dirty = 1;
pseudo_debug(PDBGF_DB, "updating device dev %llu to %llu\n",
(unsigned long long) from, (unsigned long long) to);
rc = sqlite3_step(files_update);
if (rc != SQLITE_DONE) {
dberr(file_db, "update may have failed: rc %d", rc);
}
sqlite3_reset(files_update);
sqlite3_clear_bindings(files_update);
rc = sqlite3_step(xattrs_update);
if (rc != SQLITE_DONE) {
dberr(file_db, "update may have failed: rc %d", rc);
}
sqlite3_reset(xattrs_update);
sqlite3_clear_bindings(xattrs_update);
return rc != SQLITE_DONE;
}
/* change dev/inode for a given path -- used only by RENAME for now.
* So, if you rename something with xattrs, we want to keep the xattrs.
* But. If you renamed one of multiple hard links, we may end up wanting
* to keep the xattrs on both the old name and the new one. But if there's
* only one link, we want to drop the xattrs from the old one. Eww.
*/
int
pdb_update_inode(pseudo_msg_t *msg) {
static sqlite3_stmt *update;
int rc;
static pseudo_msg_t *oldmsg;
int found_existing;
if (!oldmsg) {
oldmsg = malloc(pseudo_path_max());
}
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;
}
memcpy(oldmsg, msg, sizeof(msg) + msg->pathlen);
found_existing = !pdb_find_file_path(oldmsg);
if (found_existing) {
/* we have an existing file entry */
pdb_copy_xattrs(oldmsg, msg);
}
sqlite3_bind_int(update, 1, msg->dev);
sqlite3_bind_int64(update, 2, signed_ino(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);
if (found_existing) {
/* possibly delete old message's xattrs */
pdb_check_xattrs(oldmsg);
}
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, signed_ino(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) {
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, signed_ino(msg->ino));
rc = sqlite3_step(select);
switch (rc) {
case SQLITE_ROW:
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 | PDBGF_VERBOSE, "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) {
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:
msg->dev = sqlite3_column_int64(select, 2);
msg->ino = (ino_t) 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 | PDBGF_VERBOSE, "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, signed_ino(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 | PDBGF_VERBOSE, "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, 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, signed_ino(msg->ino));
rc = sqlite3_step(select);
switch (rc) {
case SQLITE_ROW:
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 | PDBGF_VERBOSE, "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(pseudo_msg_t *msg, char **value, size_t *len) {
static sqlite3_stmt *select;
int rc;
char *response;
size_t length;
char *sql = "SELECT value FROM xattrs WHERE dev = ? AND ino = ? 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 ino %lld\n", *value, (long long) msg->ino);
sqlite3_bind_int(select, 1, msg->dev);
sqlite3_bind_int64(select, 2, signed_ino(msg->ino));
rc = sqlite3_bind_text(select, 3, *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 | PDBGF_VERBOSE, "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(pseudo_msg_t *msg, 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 dev = ? AND ino = ? ORDER BY 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;
}
}
sqlite3_bind_int(select, 1, msg->dev);
sqlite3_bind_int64(select, 2, signed_ino(msg->ino));
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(pseudo_msg_t *msg, char *value, size_t len) {
static sqlite3_stmt *delete;
int rc;
char *sql = "DELETE FROM xattrs WHERE dev = ? AND ino = ? 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, msg->dev);
sqlite3_bind_int64(delete, 2, signed_ino(msg->ino));
rc = sqlite3_bind_text(delete, 3, 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(pseudo_msg_t *msg, 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 dev = ? AND ino = ? AND name = ?;";
char *insert_sql = "INSERT INTO xattrs "
" ( dev, ino, 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, msg->dev);
sqlite3_bind_int64(select, 2, signed_ino(msg->ino));
rc = sqlite3_bind_text(select, 3, 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 ino %lld: name is '%s' [%d/%d bytes], value is '%s' [%d bytes]. Existing row %lld.\n",
(long long) msg->ino, 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;
}
}
sqlite3_bind_int64(insert, 1, msg->dev);
sqlite3_bind_int64(insert, 2, signed_ino(msg->ino));
rc = sqlite3_bind_text(insert, 3, vname, -1, SQLITE_STATIC);
if (rc) {
dberr(file_db, "couldn't bind xattr name to INSERT statement");
return 1;
}
rc = sqlite3_bind_text(insert, 4, 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) {
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, signed_ino(msg->ino));
rc = sqlite3_step(select);
switch (rc) {
case SQLITE_ROW:
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 = (ino_t) 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);
}