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