summaryrefslogtreecommitdiffstats
path: root/utils/nfsdcld/sqlite.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/nfsdcld/sqlite.c')
-rw-r--r--utils/nfsdcld/sqlite.c1427
1 files changed, 1427 insertions, 0 deletions
diff --git a/utils/nfsdcld/sqlite.c b/utils/nfsdcld/sqlite.c
new file mode 100644
index 0000000..03016fb
--- /dev/null
+++ b/utils/nfsdcld/sqlite.c
@@ -0,0 +1,1427 @@
+/*
+ * Copyright (C) 2011 Red Hat, Jeff Layton <jlayton@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/*
+ * Explanation:
+ *
+ * This file contains the code to manage the sqlite backend database for the
+ * nfsdcld client tracking daemon.
+ *
+ * The main database is called main.sqlite and contains the following tables:
+ *
+ * parameters: simple key/value pairs for storing database info
+ *
+ * grace: a "current" column containing an INTEGER representing the current
+ * epoch (where should new values be stored) and a "recovery" column
+ * containing an INTEGER representing the recovery epoch (from what
+ * epoch are we allowed to recover). A recovery epoch of 0 means
+ * normal operation (grace period not in force). Note: sqlite stores
+ * integers as signed values, so these must be cast to a uint64_t when
+ * retrieving them from the database and back to an int64_t when storing
+ * them in the database.
+ *
+ * rec-CCCCCCCCCCCCCCCC (where C is the hex representation of the epoch value):
+ * an "id" column containing a BLOB with the long-form clientid
+ * as sent by the client, and a "princhash" column containing a BLOB
+ * with the sha256 hash of the kerberos principal (if available).
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
+#include <sqlite3.h>
+#include <linux/limits.h>
+#include <inttypes.h>
+
+#include "xlog.h"
+#include "sqlite.h"
+#include "cld.h"
+#include "cld-internal.h"
+#include "conffile.h"
+#include "legacy.h"
+#include "nfslib.h"
+
+#define CLD_SQLITE_LATEST_SCHEMA_VERSION 4
+#define CLTRACK_DEFAULT_STORAGEDIR NFS_STATEDIR "/nfsdcltrack"
+
+/* in milliseconds */
+#define CLD_SQLITE_BUSY_TIMEOUT 10000
+
+/* private data structures */
+
+/* global variables */
+static char *cltrack_storagedir = CLTRACK_DEFAULT_STORAGEDIR;
+
+/* reusable pathname and sql command buffer */
+static char buf[PATH_MAX];
+
+/* global database handle */
+static sqlite3 *dbh;
+
+/* forward declarations */
+
+/* make a directory, ignoring EEXIST errors unless it's not a directory */
+static int
+mkdir_if_not_exist(const char *dirname)
+{
+ int ret;
+ struct stat statbuf;
+
+ ret = mkdir(dirname, S_IRWXU);
+ if (ret && errno != EEXIST)
+ return -errno;
+
+ ret = stat(dirname, &statbuf);
+ if (ret)
+ return -errno;
+
+ if (!S_ISDIR(statbuf.st_mode))
+ ret = -ENOTDIR;
+
+ return ret;
+}
+
+static int
+sqlite_query_schema_version(void)
+{
+ int ret;
+ sqlite3_stmt *stmt = NULL;
+
+ /* prepare select query */
+ ret = sqlite3_prepare_v2(dbh,
+ "SELECT value FROM parameters WHERE key == \"version\";",
+ -1, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ xlog(D_GENERAL, "Unable to prepare select statement: %s",
+ sqlite3_errmsg(dbh));
+ ret = 0;
+ goto out;
+ }
+
+ /* query schema version */
+ ret = sqlite3_step(stmt);
+ if (ret != SQLITE_ROW) {
+ xlog(D_GENERAL, "Select statement execution failed: %s",
+ sqlite3_errmsg(dbh));
+ ret = 0;
+ goto out;
+ }
+
+ ret = sqlite3_column_int(stmt, 0);
+out:
+ sqlite3_finalize(stmt);
+ return ret;
+}
+
+static int
+sqlite_query_first_time(int *first_time)
+{
+ int ret;
+ sqlite3_stmt *stmt = NULL;
+
+ /* prepare select query */
+ ret = sqlite3_prepare_v2(dbh,
+ "SELECT value FROM parameters WHERE key == \"first_time\";",
+ -1, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ xlog(D_GENERAL, "Unable to prepare select statement: %s",
+ sqlite3_errmsg(dbh));
+ goto out;
+ }
+
+ /* query first_time */
+ ret = sqlite3_step(stmt);
+ if (ret != SQLITE_ROW) {
+ xlog(D_GENERAL, "Select statement execution failed: %s",
+ sqlite3_errmsg(dbh));
+ goto out;
+ }
+
+ *first_time = sqlite3_column_int(stmt, 0);
+ ret = 0;
+out:
+ sqlite3_finalize(stmt);
+ return ret;
+}
+
+static int
+sqlite_add_princ_col_cb(void *UNUSED(arg), int ncols, char **cols,
+ char **UNUSED(colnames))
+{
+ int ret;
+ char *err;
+
+ if (ncols > 1)
+ return -EINVAL;
+ ret = snprintf(buf, sizeof(buf), "ALTER TABLE \"%s\" "
+ "ADD COLUMN princhash BLOB;", cols[0]);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ return -EINVAL;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ return -EINVAL;
+ }
+ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to add princhash column to table %s: %s",
+ cols[0], err);
+ goto out;
+ }
+ xlog(D_GENERAL, "Added princhash column to table %s", cols[0]);
+out:
+ sqlite3_free(err);
+ return ret;
+}
+
+static int
+sqlite_maindb_update_v3_to_v4(void)
+{
+ int ret;
+ char *err;
+
+ ret = sqlite3_exec(dbh, "SELECT name FROM sqlite_master "
+ "WHERE type=\"table\" AND name LIKE \"%rec-%\";",
+ sqlite_add_princ_col_cb, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: Failed to update tables!: %s", __func__, err);
+ }
+ sqlite3_free(err);
+ return ret;
+}
+
+static int
+sqlite_maindb_update_v1v2_to_v4(void)
+{
+ int ret;
+ char *err;
+
+ /* create grace table */
+ ret = sqlite3_exec(dbh, "CREATE TABLE grace "
+ "(current INTEGER , recovery INTEGER);",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to create grace table: %s", err);
+ goto out;
+ }
+
+ /* insert initial epochs into grace table */
+ ret = sqlite3_exec(dbh, "INSERT OR FAIL INTO grace "
+ "values (1, 0);",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to set initial epochs: %s", err);
+ goto out;
+ }
+
+ /* create recovery table for current epoch */
+ ret = sqlite3_exec(dbh, "CREATE TABLE \"rec-0000000000000001\" "
+ "(id BLOB PRIMARY KEY, princhash BLOB);",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to create recovery table "
+ "for current epoch: %s", err);
+ goto out;
+ }
+
+ /* copy records from old clients table */
+ ret = sqlite3_exec(dbh, "INSERT INTO \"rec-0000000000000001\" (id) "
+ "SELECT id FROM clients;",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to copy client records: %s", err);
+ goto out;
+ }
+
+ /* drop the old clients table */
+ ret = sqlite3_exec(dbh, "DROP TABLE clients;",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to drop old clients table: %s", err);
+ }
+out:
+ sqlite3_free(err);
+ return ret;
+}
+
+static int
+sqlite_maindb_update_schema(int oldversion)
+{
+ int ret, ret2;
+ char *err;
+
+ /* begin transaction */
+ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL,
+ &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to begin transaction: %s", err);
+ goto rollback;
+ }
+
+ /*
+ * Check schema version again. This time, under an exclusive
+ * transaction to guard against racing DB setup attempts
+ */
+ ret = sqlite_query_schema_version();
+ if (ret != oldversion) {
+ if (ret == CLD_SQLITE_LATEST_SCHEMA_VERSION)
+ /* Someone else raced in and set it up */
+ ret = 0;
+ else
+ /* Something went wrong -- fail! */
+ ret = -EINVAL;
+ goto rollback;
+ }
+
+ /* Still at old version -- do conversion */
+
+ switch (oldversion) {
+ case 3:
+ case 2:
+ ret = sqlite_maindb_update_v3_to_v4();
+ break;
+ case 1:
+ ret = sqlite_maindb_update_v1v2_to_v4();
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ if (ret != SQLITE_OK)
+ goto rollback;
+
+ ret = snprintf(buf, sizeof(buf), "UPDATE parameters SET value = %d "
+ "WHERE key = \"version\";",
+ CLD_SQLITE_LATEST_SCHEMA_VERSION);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ goto rollback;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ ret = -EINVAL;
+ goto rollback;
+ }
+
+ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to update schema version: %s", err);
+ goto rollback;
+ }
+
+ ret = sqlite_query_first_time(&first_time);
+ if (ret != SQLITE_OK) {
+ /* insert first_time into parameters table */
+ ret = sqlite3_exec(dbh, "INSERT OR FAIL INTO parameters "
+ "values (\"first_time\", \"1\");",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to insert into parameter table: %s", err);
+ goto rollback;
+ }
+ }
+
+ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to commit transaction: %s", err);
+ goto rollback;
+ }
+out:
+ sqlite3_free(err);
+ return ret;
+rollback:
+ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err);
+ if (ret2 != SQLITE_OK)
+ xlog(L_ERROR, "Unable to rollback transaction: %s", err);
+ goto out;
+}
+
+/*
+ * Start an exclusive transaction and recheck the DB schema version. If it's
+ * still zero (indicating a new database) then set it up. If that all works,
+ * then insert schema version into the parameters table and commit the
+ * transaction. On any error, rollback the transaction.
+ */
+static int
+sqlite_maindb_init_v4(void)
+{
+ int ret, ret2;
+ char *err = NULL;
+
+ /* Start a transaction */
+ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL,
+ &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to begin transaction: %s", err);
+ goto out;
+ }
+
+ /*
+ * Check schema version again. This time, under an exclusive
+ * transaction to guard against racing DB setup attempts
+ */
+ ret = sqlite_query_schema_version();
+ switch (ret) {
+ case 0:
+ /* Query failed again -- set up DB */
+ break;
+ case CLD_SQLITE_LATEST_SCHEMA_VERSION:
+ /* Someone else raced in and set it up */
+ ret = 0;
+ goto rollback;
+ default:
+ /* Something went wrong -- fail! */
+ ret = -EINVAL;
+ goto rollback;
+ }
+
+ ret = sqlite3_exec(dbh, "CREATE TABLE parameters "
+ "(key TEXT PRIMARY KEY, value TEXT);",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to create parameter table: %s", err);
+ goto rollback;
+ }
+
+ /* create grace table */
+ ret = sqlite3_exec(dbh, "CREATE TABLE grace "
+ "(current INTEGER , recovery INTEGER);",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to create grace table: %s", err);
+ goto rollback;
+ }
+
+ /* insert initial epochs into grace table */
+ ret = sqlite3_exec(dbh, "INSERT OR FAIL INTO grace "
+ "values (1, 0);",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to set initial epochs: %s", err);
+ goto rollback;
+ }
+
+ /* create recovery table for current epoch */
+ ret = sqlite3_exec(dbh, "CREATE TABLE \"rec-0000000000000001\" "
+ "(id BLOB PRIMARY KEY, princhash BLOB);",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to create recovery table "
+ "for current epoch: %s", err);
+ goto rollback;
+ }
+
+ /* insert version into parameters table */
+ ret = snprintf(buf, sizeof(buf), "INSERT OR FAIL INTO parameters "
+ "values (\"version\", \"%d\");",
+ CLD_SQLITE_LATEST_SCHEMA_VERSION);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ goto rollback;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ ret = -EINVAL;
+ goto rollback;
+ }
+
+ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to insert into parameter table: %s", err);
+ goto rollback;
+ }
+
+ /* insert first_time into parameters table */
+ ret = sqlite3_exec(dbh, "INSERT OR FAIL INTO parameters "
+ "values (\"first_time\", \"1\");",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to insert into parameter table: %s", err);
+ goto rollback;
+ }
+
+ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to commit transaction: %s", err);
+ goto rollback;
+ }
+out:
+ sqlite3_free(err);
+ return ret;
+
+rollback:
+ /* Attempt to rollback the transaction */
+ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err);
+ if (ret2 != SQLITE_OK)
+ xlog(L_ERROR, "Unable to rollback transaction: %s", err);
+ goto out;
+}
+
+static int
+sqlite_startup_query_grace(void)
+{
+ int ret;
+ uint64_t tcur;
+ uint64_t trec;
+ sqlite3_stmt *stmt = NULL;
+
+ /* prepare select query */
+ ret = sqlite3_prepare_v2(dbh, "SELECT * FROM grace;", -1, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ xlog(D_GENERAL, "Unable to prepare select statement: %s",
+ sqlite3_errmsg(dbh));
+ goto out;
+ }
+
+ ret = sqlite3_step(stmt);
+ if (ret != SQLITE_ROW) {
+ xlog(D_GENERAL, "Select statement execution failed: %s",
+ sqlite3_errmsg(dbh));
+ goto out;
+ }
+
+ tcur = (uint64_t)sqlite3_column_int64(stmt, 0);
+ trec = (uint64_t)sqlite3_column_int64(stmt, 1);
+
+ current_epoch = tcur;
+ recovery_epoch = trec;
+ ret = 0;
+ xlog(D_GENERAL, "%s: current_epoch=%"PRIu64" recovery_epoch=%"PRIu64,
+ __func__, current_epoch, recovery_epoch);
+out:
+ sqlite3_finalize(stmt);
+ return ret;
+}
+
+/*
+ * Helper for renaming a recovery table to fix the padding.
+ */
+static int
+sqlite_fix_table_name(const char *name)
+{
+ int ret;
+ uint64_t val;
+ char *err;
+
+ if (sscanf(name, "rec-%" PRIx64, &val) != 1)
+ return -EINVAL;
+ ret = snprintf(buf, sizeof(buf), "ALTER TABLE \"%s\" "
+ "RENAME TO \"rec-%016" PRIx64 "\";",
+ name, val);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ return -EINVAL;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ return -EINVAL;
+ }
+ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to fix table for epoch %"PRIu64": %s",
+ val, err);
+ goto out;
+ }
+ xlog(D_GENERAL, "Renamed table %s to rec-%016" PRIx64, name, val);
+out:
+ sqlite3_free(err);
+ return ret;
+}
+
+/*
+ * Callback for the sqlite_exec statement in sqlite_check_table_names.
+ * If the epoch encoded in the table name matches either the current
+ * epoch or the recovery epoch, then try to fix the padding. Otherwise,
+ * we bail.
+ */
+static int
+sqlite_check_table_names_cb(void *UNUSED(arg), int ncols, char **cols,
+ char **UNUSED(colnames))
+{
+ int ret = SQLITE_OK;
+ uint64_t val;
+
+ if (ncols > 1)
+ return -EINVAL;
+ if (sscanf(cols[0], "rec-%" PRIx64, &val) != 1)
+ return -EINVAL;
+ if (val == current_epoch || val == recovery_epoch) {
+ xlog(D_GENERAL, "found invalid table name %s for %s epoch",
+ cols[0], val == current_epoch ? "current" : "recovery");
+ ret = sqlite_fix_table_name(cols[0]);
+ } else {
+ xlog(L_ERROR, "found invalid table name %s for unknown epoch %"
+ PRId64, cols[0], val);
+ return -EINVAL;
+ }
+ return ret;
+}
+
+/*
+ * Look for recovery table names where the epoch isn't zero-padded
+ */
+static int
+sqlite_check_table_names(void)
+{
+ int ret;
+ char *err;
+
+ ret = sqlite3_exec(dbh, "SELECT name FROM sqlite_master "
+ "WHERE type=\"table\" AND name LIKE \"%rec-%\" "
+ "AND length(name) < 20;",
+ sqlite_check_table_names_cb, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Table names check failed: %s", err);
+ }
+ sqlite3_free(err);
+ return ret;
+}
+
+/*
+ * Simple db health check. For now we're just making sure that the recovery
+ * table names are of the format "rec-CCCCCCCCCCCCCCCC" (where C is the hex
+ * representation of the epoch value) and that epoch value matches either
+ * the current epoch or the recovery epoch.
+ */
+static int
+sqlite_check_db_health(void)
+{
+ int ret, ret2;
+ char *err;
+
+ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL,
+ &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to begin transaction: %s", err);
+ goto rollback;
+ }
+
+ ret = sqlite_check_table_names();
+ if (ret != SQLITE_OK)
+ goto rollback;
+
+ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to commit transaction: %s", err);
+ goto rollback;
+ }
+
+cleanup:
+ sqlite3_free(err);
+ xlog(D_GENERAL, "%s: returning %d", __func__, ret);
+ return ret;
+rollback:
+ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err);
+ if (ret2 != SQLITE_OK)
+ xlog(L_ERROR, "Unable to rollback transaction: %s", err);
+ goto cleanup;
+}
+
+static int
+sqlite_attach_db(const char *path)
+{
+ int ret;
+ char dbpath[PATH_MAX];
+ struct stat stb;
+ sqlite3_stmt *stmt = NULL;
+
+ ret = snprintf(dbpath, PATH_MAX - 1, "%s/main.sqlite", path);
+ if (ret < 0)
+ return ret;
+
+ dbpath[PATH_MAX - 1] = '\0';
+ ret = stat(dbpath, &stb);
+ if (ret < 0)
+ return ret;
+
+ xlog(D_GENERAL, "attaching %s", dbpath);
+ ret = sqlite3_prepare_v2(dbh, "ATTACH DATABASE ? AS attached;",
+ -1, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: unable to prepare attach statement: %s",
+ __func__, sqlite3_errmsg(dbh));
+ return ret;
+ }
+
+ ret = sqlite3_bind_text(stmt, 1, dbpath, strlen(dbpath), SQLITE_STATIC);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: bind text failed: %s",
+ __func__, sqlite3_errmsg(dbh));
+ return ret;
+ }
+
+ ret = sqlite3_step(stmt);
+ if (ret == SQLITE_DONE)
+ ret = SQLITE_OK;
+ else
+ xlog(L_ERROR, "%s: unexpected return code from attach: %s",
+ __func__, sqlite3_errmsg(dbh));
+
+ sqlite3_finalize(stmt);
+ stmt = NULL;
+ return ret;
+}
+
+static int
+sqlite_detach_db(void)
+{
+ int ret;
+ char *err = NULL;
+
+ xlog(D_GENERAL, "detaching database");
+ ret = sqlite3_exec(dbh, "DETACH DATABASE attached;", NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to detach attached db: %s", err);
+ }
+
+ sqlite3_free(err);
+ return ret;
+}
+
+/*
+ * Copies client records from the nfsdcltrack database as part of a one-time
+ * "upgrade".
+ *
+ * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0).
+ * Returns the number of records copied via "num_rec".
+ */
+static int
+sqlite_copy_cltrack_records(int *num_rec)
+{
+ int ret, ret2;
+ char *s;
+ char *err = NULL;
+ sqlite3_stmt *stmt = NULL;
+
+ s = conf_get_str("nfsdcltrack", "storagedir");
+ if (s)
+ cltrack_storagedir = s;
+ ret = sqlite_attach_db(cltrack_storagedir);
+ if (ret)
+ goto out;
+ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL,
+ &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to begin transaction: %s", err);
+ goto rollback;
+ }
+ ret = snprintf(buf, sizeof(buf), "DELETE FROM \"rec-%016" PRIx64 "\";",
+ current_epoch);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ goto rollback;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ ret = -EINVAL;
+ goto rollback;
+ }
+ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to clear records from current epoch: %s", err);
+ goto rollback;
+ }
+ ret = snprintf(buf, sizeof(buf), "INSERT INTO \"rec-%016" PRIx64 "\" (id) "
+ "SELECT id FROM attached.clients;",
+ current_epoch);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ goto rollback;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ ret = -EINVAL;
+ goto rollback;
+ }
+ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: insert statement prepare failed: %s",
+ __func__, sqlite3_errmsg(dbh));
+ goto rollback;
+ }
+ ret = sqlite3_step(stmt);
+ if (ret != SQLITE_DONE) {
+ xlog(L_ERROR, "%s: unexpected return code from insert: %s",
+ __func__, sqlite3_errmsg(dbh));
+ goto rollback;
+ }
+ *num_rec = sqlite3_changes(dbh);
+ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to commit transaction: %s", err);
+ goto rollback;
+ }
+cleanup:
+ sqlite3_finalize(stmt);
+ sqlite3_free(err);
+ sqlite_detach_db();
+out:
+ xlog(D_GENERAL, "%s: returning %d", __func__, ret);
+ return ret;
+rollback:
+ *num_rec = 0;
+ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err);
+ if (ret2 != SQLITE_OK)
+ xlog(L_ERROR, "Unable to rollback transaction: %s", err);
+ goto cleanup;
+}
+
+/* Open the database and set up the database handle for it */
+int
+sqlite_prepare_dbh(const char *topdir)
+{
+ int ret;
+
+ /* Do nothing if the database handle is already set up */
+ if (dbh)
+ return 0;
+
+ ret = snprintf(buf, PATH_MAX - 1, "%s/main.sqlite", topdir);
+ if (ret < 0)
+ return ret;
+
+ buf[PATH_MAX - 1] = '\0';
+
+ /* open a new DB handle */
+ ret = sqlite3_open(buf, &dbh);
+ if (ret != SQLITE_OK) {
+ /* try to create the dir */
+ ret = mkdir_if_not_exist(topdir);
+ if (ret)
+ goto out_close;
+
+ /* retry open */
+ ret = sqlite3_open(buf, &dbh);
+ if (ret != SQLITE_OK)
+ goto out_close;
+ }
+
+ /* set busy timeout */
+ ret = sqlite3_busy_timeout(dbh, CLD_SQLITE_BUSY_TIMEOUT);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to set sqlite busy timeout: %s",
+ sqlite3_errmsg(dbh));
+ goto out_close;
+ }
+
+ ret = sqlite_query_schema_version();
+ switch (ret) {
+ case CLD_SQLITE_LATEST_SCHEMA_VERSION:
+ /* DB is already set up. Do nothing */
+ break;
+ case 3:
+ /* Old DB -- update to new schema */
+ ret = sqlite_maindb_update_schema(3);
+ if (ret)
+ goto out_close;
+ break;
+ case 2:
+ /* Old DB -- update to new schema */
+ ret = sqlite_maindb_update_schema(2);
+ if (ret)
+ goto out_close;
+ break;
+
+ case 1:
+ /* Old DB -- update to new schema */
+ ret = sqlite_maindb_update_schema(1);
+ if (ret)
+ goto out_close;
+ break;
+ case 0:
+ /* Query failed -- try to set up new DB */
+ ret = sqlite_maindb_init_v4();
+ if (ret)
+ goto out_close;
+ break;
+ default:
+ /* Unknown DB version -- downgrade? Fail */
+ xlog(L_ERROR, "Unsupported database schema version! "
+ "Expected %d, got %d.",
+ CLD_SQLITE_LATEST_SCHEMA_VERSION, ret);
+ ret = -EINVAL;
+ goto out_close;
+ }
+
+ ret = sqlite_startup_query_grace();
+ if (ret)
+ goto out_close;
+
+ ret = sqlite_query_first_time(&first_time);
+ if (ret)
+ goto out_close;
+
+ ret = sqlite_check_db_health();
+ if (ret) {
+ xlog(L_ERROR, "Database health check failed! "
+ "Database must be fixed manually.");
+ goto out_close;
+ }
+
+ /* one-time "upgrade" from older client tracking methods */
+ if (first_time) {
+ sqlite_copy_cltrack_records(&num_cltrack_records);
+ xlog(D_GENERAL, "%s: num_cltrack_records = %d\n",
+ __func__, num_cltrack_records);
+ legacy_load_clients_from_recdir(&num_legacy_records);
+ xlog(D_GENERAL, "%s: num_legacy_records = %d\n",
+ __func__, num_legacy_records);
+ if (num_cltrack_records > 0 && num_legacy_records > 0)
+ xlog(L_WARNING, "%s: first-time upgrade detected "
+ "both cltrack and legacy records!\n", __func__);
+ }
+
+ return ret;
+out_close:
+ sqlite3_close(dbh);
+ dbh = NULL;
+ return ret;
+}
+
+/*
+ * Create a client record
+ *
+ * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0)
+ */
+int
+sqlite_insert_client(const unsigned char *clname, const size_t namelen)
+{
+ int ret;
+ sqlite3_stmt *stmt = NULL;
+
+ ret = snprintf(buf, sizeof(buf), "INSERT OR REPLACE INTO \"rec-%016" PRIx64 "\" (id) "
+ "VALUES (?);", current_epoch);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ return ret;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ return -EINVAL;
+ }
+
+ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: insert statement prepare failed: %s",
+ __func__, sqlite3_errmsg(dbh));
+ return ret;
+ }
+
+ ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
+ SQLITE_STATIC);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: bind blob failed: %s", __func__,
+ sqlite3_errmsg(dbh));
+ goto out_err;
+ }
+
+ ret = sqlite3_step(stmt);
+ if (ret == SQLITE_DONE)
+ ret = SQLITE_OK;
+ else
+ xlog(L_ERROR, "%s: unexpected return code from insert: %s",
+ __func__, sqlite3_errmsg(dbh));
+
+out_err:
+ xlog(D_GENERAL, "%s: returning %d", __func__, ret);
+ sqlite3_finalize(stmt);
+ return ret;
+}
+
+#if UPCALL_VERSION >= 2
+/*
+ * Create a client record including hash the kerberos principal
+ *
+ * Returns a non-zero sqlite error code, or SQLITE_OK (aka 0)
+ */
+int
+sqlite_insert_client_and_princhash(const unsigned char *clname, const size_t namelen,
+ const unsigned char *clprinchash, const size_t princhashlen)
+{
+ int ret;
+ sqlite3_stmt *stmt = NULL;
+
+ if (princhashlen > 0)
+ ret = snprintf(buf, sizeof(buf), "INSERT OR REPLACE INTO \"rec-%016" PRIx64 "\" "
+ "VALUES (?, ?);", current_epoch);
+ else
+ ret = snprintf(buf, sizeof(buf), "INSERT OR REPLACE INTO \"rec-%016" PRIx64 "\" (id) "
+ "VALUES (?);", current_epoch);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ return ret;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ return -EINVAL;
+ }
+
+ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: insert statement prepare failed: %s",
+ __func__, sqlite3_errmsg(dbh));
+ return ret;
+ }
+
+ ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
+ SQLITE_STATIC);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: bind blob failed: %s", __func__,
+ sqlite3_errmsg(dbh));
+ goto out_err;
+ }
+
+ if (princhashlen > 0) {
+ ret = sqlite3_bind_blob(stmt, 2, (const void *)clprinchash, princhashlen,
+ SQLITE_STATIC);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: bind blob failed: %s", __func__,
+ sqlite3_errmsg(dbh));
+ goto out_err;
+ }
+ }
+
+ ret = sqlite3_step(stmt);
+ if (ret == SQLITE_DONE)
+ ret = SQLITE_OK;
+ else
+ xlog(L_ERROR, "%s: unexpected return code from insert: %s",
+ __func__, sqlite3_errmsg(dbh));
+
+out_err:
+ xlog(D_GENERAL, "%s: returning %d", __func__, ret);
+ sqlite3_finalize(stmt);
+ return ret;
+}
+#else
+int
+sqlite_insert_client_and_princhash(const unsigned char *clname, const size_t namelen,
+ const unsigned char *clprinchash, const size_t princhashlen)
+{
+ return -EINVAL;
+}
+#endif
+
+/* Remove a client record */
+int
+sqlite_remove_client(const unsigned char *clname, const size_t namelen)
+{
+ int ret;
+ sqlite3_stmt *stmt = NULL;
+
+ ret = snprintf(buf, sizeof(buf), "DELETE FROM \"rec-%016" PRIx64 "\" "
+ "WHERE id==?;", current_epoch);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ return ret;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ return -EINVAL;
+ }
+
+ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL);
+
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: statement prepare failed: %s",
+ __func__, sqlite3_errmsg(dbh));
+ goto out_err;
+ }
+
+ ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
+ SQLITE_STATIC);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: bind blob failed: %s", __func__,
+ sqlite3_errmsg(dbh));
+ goto out_err;
+ }
+
+ ret = sqlite3_step(stmt);
+ if (ret == SQLITE_DONE)
+ ret = SQLITE_OK;
+ else
+ xlog(L_ERROR, "%s: unexpected return code from delete: %d",
+ __func__, ret);
+
+out_err:
+ xlog(D_GENERAL, "%s: returning %d", __func__, ret);
+ sqlite3_finalize(stmt);
+ return ret;
+}
+
+/*
+ * Is the given clname in the clients table? If so, then update its timestamp
+ * and return success. If the record isn't present, or the update fails, then
+ * return an error.
+ */
+int
+sqlite_check_client(const unsigned char *clname, const size_t namelen)
+{
+ int ret;
+ sqlite3_stmt *stmt = NULL;
+
+ ret = snprintf(buf, sizeof(buf), "SELECT count(*) FROM \"rec-%016" PRIx64 "\" "
+ "WHERE id==?;", recovery_epoch);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ return ret;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ return -EINVAL;
+ }
+
+ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: select statement prepare failed: %s",
+ __func__, sqlite3_errmsg(dbh));
+ return ret;
+ }
+
+ ret = sqlite3_bind_blob(stmt, 1, (const void *)clname, namelen,
+ SQLITE_STATIC);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: bind blob failed: %s",
+ __func__, sqlite3_errmsg(dbh));
+ goto out_err;
+ }
+
+ ret = sqlite3_step(stmt);
+ if (ret != SQLITE_ROW) {
+ xlog(L_ERROR, "%s: unexpected return code from select: %d",
+ __func__, ret);
+ goto out_err;
+ }
+
+ ret = sqlite3_column_int(stmt, 0);
+ xlog(D_GENERAL, "%s: select returned %d rows", __func__, ret);
+ if (ret != 1) {
+ ret = -EACCES;
+ goto out_err;
+ }
+
+ sqlite3_finalize(stmt);
+
+ /* Now insert the client into the table for the current epoch */
+ return sqlite_insert_client(clname, namelen);
+
+out_err:
+ xlog(D_GENERAL, "%s: returning %d", __func__, ret);
+ sqlite3_finalize(stmt);
+ return ret;
+}
+
+int
+sqlite_grace_start(void)
+{
+ int ret, ret2;
+ char *err;
+ uint64_t tcur = current_epoch;
+ uint64_t trec = recovery_epoch;
+
+ /* begin transaction */
+ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL,
+ &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to begin transaction: %s", err);
+ goto rollback;
+ }
+
+ if (trec == 0) {
+ /*
+ * A normal grace start - update the epoch values in the grace
+ * table and create a new table for the current reboot epoch.
+ */
+ trec = tcur;
+ tcur++;
+
+ ret = snprintf(buf, sizeof(buf), "UPDATE grace "
+ "SET current = %" PRId64 ", recovery = %" PRId64 ";",
+ (int64_t)tcur, (int64_t)trec);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ goto rollback;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)",
+ ret);
+ ret = -EINVAL;
+ goto rollback;
+ }
+
+ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to update epochs: %s", err);
+ goto rollback;
+ }
+
+ ret = snprintf(buf, sizeof(buf), "CREATE TABLE \"rec-%016" PRIx64 "\" "
+ "(id BLOB PRIMARY KEY, princhash blob);",
+ tcur);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ goto rollback;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)",
+ ret);
+ ret = -EINVAL;
+ goto rollback;
+ }
+
+ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to create table for current epoch: %s",
+ err);
+ goto rollback;
+ }
+ } else {
+ /* Server restarted while in grace - don't update the epoch
+ * values in the grace table, just clear out the records for
+ * the current reboot epoch.
+ */
+ ret = snprintf(buf, sizeof(buf), "DELETE FROM \"rec-%016" PRIx64 "\";",
+ tcur);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ goto rollback;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ ret = -EINVAL;
+ goto rollback;
+ }
+
+ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to clear table for current epoch: %s",
+ err);
+ goto rollback;
+ }
+ }
+
+ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to commit transaction: %s", err);
+ goto rollback;
+ }
+
+ current_epoch = tcur;
+ recovery_epoch = trec;
+ xlog(D_GENERAL, "%s: current_epoch=%"PRIu64" recovery_epoch=%"PRIu64,
+ __func__, current_epoch, recovery_epoch);
+
+out:
+ sqlite3_free(err);
+ return ret;
+rollback:
+ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err);
+ if (ret2 != SQLITE_OK)
+ xlog(L_ERROR, "Unable to rollback transaction: %s", err);
+ goto out;
+}
+
+int
+sqlite_grace_done(void)
+{
+ int ret, ret2;
+ char *err;
+
+ /* begin transaction */
+ ret = sqlite3_exec(dbh, "BEGIN EXCLUSIVE TRANSACTION;", NULL, NULL,
+ &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to begin transaction: %s", err);
+ goto rollback;
+ }
+
+ ret = sqlite3_exec(dbh, "UPDATE grace SET recovery = \"0\";",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to clear recovery epoch: %s", err);
+ goto rollback;
+ }
+
+ ret = snprintf(buf, sizeof(buf), "DROP TABLE \"rec-%016" PRIx64 "\";",
+ recovery_epoch);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ goto rollback;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ ret = -EINVAL;
+ goto rollback;
+ }
+
+ ret = sqlite3_exec(dbh, (const char *)buf, NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to drop table for recovery epoch: %s",
+ err);
+ goto rollback;
+ }
+
+ ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to commit transaction: %s", err);
+ goto rollback;
+ }
+
+ recovery_epoch = 0;
+ xlog(D_GENERAL, "%s: current_epoch=%"PRIu64" recovery_epoch=%"PRIu64,
+ __func__, current_epoch, recovery_epoch);
+
+out:
+ sqlite3_free(err);
+ return ret;
+rollback:
+ ret2 = sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err);
+ if (ret2 != SQLITE_OK)
+ xlog(L_ERROR, "Unable to rollback transaction: %s", err);
+ goto out;
+}
+
+
+int
+sqlite_iterate_recovery(int (*cb)(struct cld_client *clnt), struct cld_client *clnt)
+{
+ int ret;
+ sqlite3_stmt *stmt = NULL;
+#if UPCALL_VERSION >= 2
+ struct cld_msg_v2 *cmsg = &clnt->cl_u.cl_msg_v2;
+#else
+ struct cld_msg *cmsg = &clnt->cl_u.cl_msg;
+#endif
+
+ if (recovery_epoch == 0) {
+ xlog(D_GENERAL, "%s: not in grace!", __func__);
+ return -EINVAL;
+ }
+
+ ret = snprintf(buf, sizeof(buf), "SELECT * FROM \"rec-%016" PRIx64 "\";",
+ recovery_epoch);
+ if (ret < 0) {
+ xlog(L_ERROR, "sprintf failed!");
+ return ret;
+ } else if ((size_t)ret >= sizeof(buf)) {
+ xlog(L_ERROR, "sprintf output too long! (%d chars)", ret);
+ return -EINVAL;
+ }
+
+ ret = sqlite3_prepare_v2(dbh, buf, -1, &stmt, NULL);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "%s: select statement prepare failed: %s",
+ __func__, sqlite3_errmsg(dbh));
+ return ret;
+ }
+
+ while ((ret = sqlite3_step(stmt)) == SQLITE_ROW) {
+ const void *id;
+ int id_len;
+
+ id = sqlite3_column_blob(stmt, 0);
+ id_len = sqlite3_column_bytes(stmt, 0);
+ if (id_len > NFS4_OPAQUE_LIMIT)
+ id_len = NFS4_OPAQUE_LIMIT;
+
+ memset(&cmsg->cm_u, 0, sizeof(cmsg->cm_u));
+#if UPCALL_VERSION >= 2
+ memcpy(&cmsg->cm_u.cm_clntinfo.cc_name.cn_id, id, id_len);
+ cmsg->cm_u.cm_clntinfo.cc_name.cn_len = id_len;
+ if (sqlite3_column_bytes(stmt, 1) > 0) {
+ memcpy(&cmsg->cm_u.cm_clntinfo.cc_princhash.cp_data,
+ sqlite3_column_blob(stmt, 1), SHA256_DIGEST_SIZE);
+ cmsg->cm_u.cm_clntinfo.cc_princhash.cp_len = sqlite3_column_bytes(stmt, 1);
+ }
+#else
+ memcpy(&cmsg->cm_u.cm_name.cn_id, id, id_len);
+ cmsg->cm_u.cm_name.cn_len = id_len;
+#endif
+ cb(clnt);
+ }
+ if (ret == SQLITE_DONE)
+ ret = 0;
+ sqlite3_finalize(stmt);
+ return ret;
+}
+
+/*
+ * Cleans out the old nfsdcltrack database.
+ *
+ * Called upon receipt of the first "GraceDone" upcall only.
+ */
+int
+sqlite_delete_cltrack_records(void)
+{
+ int ret;
+ char *s;
+ char *err = NULL;
+
+ s = conf_get_str("nfsdcltrack", "storagedir");
+ if (s)
+ cltrack_storagedir = s;
+ ret = sqlite_attach_db(cltrack_storagedir);
+ if (ret)
+ goto out;
+ ret = sqlite3_exec(dbh, "DELETE FROM attached.clients;",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK) {
+ xlog(L_ERROR, "Unable to clear records from cltrack db: %s",
+ err);
+ }
+ sqlite_detach_db();
+out:
+ sqlite3_free(err);
+ return ret;
+}
+
+/*
+ * Sets first_time to 0 in the parameters table to ensure we only
+ * copy old client tracking records into the database one time.
+ *
+ * Called upon receipt of the first "GraceDone" upcall only.
+ */
+int
+sqlite_first_time_done(void)
+{
+ int ret;
+ char *err = NULL;
+
+ ret = sqlite3_exec(dbh, "UPDATE parameters SET value = \"0\" "
+ "WHERE key = \"first_time\";",
+ NULL, NULL, &err);
+ if (ret != SQLITE_OK)
+ xlog(L_ERROR, "Unable to clear first_time: %s", err);
+
+ sqlite3_free(err);
+ return ret;
+}
+
+/*
+ * Closes all sqlite3 resources and shuts down the library.
+ *
+ */
+void
+sqlite_shutdown(void)
+{
+ if (dbh != NULL) {
+ sqlite3_close(dbh);
+ dbh = NULL;
+ }
+
+ sqlite3_shutdown();
+}