/*
 * 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;
		if (id_len == 0) {
			xlog(L_ERROR, "%s: Skipping client record with null id",
				__func__);
			continue;
		}

		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();
}
