diff options
-rw-r--r-- | utils/nfsdcltrack/nfsdcltrack.c | 2 | ||||
-rw-r--r-- | utils/nfsdcltrack/sqlite.c | 223 | ||||
-rw-r--r-- | utils/nfsdcltrack/sqlite.h | 1 |
3 files changed, 141 insertions, 85 deletions
diff --git a/utils/nfsdcltrack/nfsdcltrack.c b/utils/nfsdcltrack/nfsdcltrack.c index 4334340..f281361 100644 --- a/utils/nfsdcltrack/nfsdcltrack.c +++ b/utils/nfsdcltrack/nfsdcltrack.c @@ -241,7 +241,7 @@ cltrack_init(const char __attribute__((unused)) *unused) } /* set up storage db */ - ret = sqlite_maindb_init(storagedir); + ret = sqlite_prepare_dbh(storagedir); if (ret) { xlog(L_ERROR, "Failed to init database: %d", ret); /* diff --git a/utils/nfsdcltrack/sqlite.c b/utils/nfsdcltrack/sqlite.c index 1c8d935..dde2f7f 100644 --- a/utils/nfsdcltrack/sqlite.c +++ b/utils/nfsdcltrack/sqlite.c @@ -88,135 +88,192 @@ mkdir_if_not_exist(const char *dirname) return ret; } -/* Open the database and set up the database handle for it */ -int -sqlite_prepare_dbh(const char *topdir) +static int +sqlite_query_schema_version(void) { int ret; + sqlite3_stmt *stmt = NULL; - /* 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'; - - ret = sqlite3_open(buf, &dbh); + /* prepare select query */ + ret = sqlite3_prepare_v2(dbh, + "SELECT value FROM parameters WHERE key == \"version\";", + -1, &stmt, NULL); if (ret != SQLITE_OK) { - xlog(L_ERROR, "Unable to open main database: %d", ret); - dbh = NULL; - return ret; + xlog(L_ERROR, "Unable to prepare select statement: %s", + sqlite3_errstr(ret)); + ret = 0; + goto out; } - ret = sqlite3_busy_timeout(dbh, CLTRACK_SQLITE_BUSY_TIMEOUT); - if (ret != SQLITE_OK) { - xlog(L_ERROR, "Unable to set sqlite busy timeout: %d", ret); - sqlite3_close(dbh); - dbh = NULL; + /* query schema version */ + ret = sqlite3_step(stmt); + if (ret != SQLITE_ROW) { + xlog(L_ERROR, "Select statement execution failed: %s", + sqlite3_errstr(ret)); + ret = 0; + goto out; } + ret = sqlite3_column_int(stmt, 0); +out: + sqlite3_finalize(stmt); return ret; } /* - * Open the "main" database, and attempt to initialize it by creating the - * parameters table and inserting the schema version into it. Ignore any errors - * from that, and then attempt to select the version out of it again. If the - * version appears wrong, then assume that the DB is corrupt or has been - * upgraded, and return an error. If all of that works, then attempt to create - * the "clients" table. + * 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. */ int -sqlite_maindb_init(const char *topdir) +sqlite_maindb_init_v1(void) { int ret; char *err = NULL; - sqlite3_stmt *stmt = NULL; - ret = mkdir_if_not_exist(topdir); - if (ret) + /* 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); return ret; + } - ret = sqlite_prepare_dbh(topdir); - if (ret) - return ret; + /* + * 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 CLTRACK_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; + } - /* Try to create table */ - ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS parameters " + 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: %d", ret); - goto out_err; + xlog(L_ERROR, "Unable to create parameter table: %s", err); + goto rollback; } - /* insert version into table -- ignore error if it fails */ - ret = snprintf(buf, sizeof(buf), - "INSERT OR IGNORE INTO parameters values (\"version\", " - "\"%d\");", CLTRACK_SQLITE_LATEST_SCHEMA_VERSION); + /* create the "clients" table */ + ret = sqlite3_exec(dbh, "CREATE TABLE clients (id BLOB PRIMARY KEY, " + "time INTEGER);", + NULL, NULL, &err); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "Unable to create clients table: %s", err); + goto rollback; + } + + + /* insert version into parameters table */ + ret = snprintf(buf, sizeof(buf), "INSERT OR FAIL INTO parameters " + "values (\"version\", \"%d\");", + CLTRACK_SQLITE_LATEST_SCHEMA_VERSION); if (ret < 0) { - goto out_err; + 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 out_err; + 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: %d", - ret); - goto out_err; + xlog(L_ERROR, "Unable to insert into parameter table: %s", err); + goto rollback; } - ret = sqlite3_prepare_v2(dbh, - "SELECT value FROM parameters WHERE key == \"version\";", - -1, &stmt, NULL); + ret = sqlite3_exec(dbh, "COMMIT TRANSACTION;", NULL, NULL, &err); if (ret != SQLITE_OK) { - xlog(L_ERROR, "Unable to prepare select statement: %d", ret); - goto out_err; + xlog(L_ERROR, "Unable to commit transaction: %s", err); + goto rollback; } +out: + sqlite3_free(err); + return ret; - /* check schema version */ - ret = sqlite3_step(stmt); - if (ret != SQLITE_ROW) { - xlog(L_ERROR, "Select statement execution failed: %s", - sqlite3_errmsg(dbh)); - goto out_err; +rollback: + /* Attempt to rollback the transaction */ + sqlite3_exec(dbh, "ROLLBACK TRANSACTION;", NULL, NULL, &err); + goto out; +} + +/* 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; } - /* process SELECT result */ - ret = sqlite3_column_int(stmt, 0); - if (ret != CLTRACK_SQLITE_LATEST_SCHEMA_VERSION) { + /* set busy timeout */ + ret = sqlite3_busy_timeout(dbh, CLTRACK_SQLITE_BUSY_TIMEOUT); + if (ret != SQLITE_OK) { + xlog(L_ERROR, "Unable to set sqlite busy timeout: %s", + sqlite3_errstr(ret)); + goto out_close; + } + + ret = sqlite_query_schema_version(); + switch (ret) { + case CLTRACK_SQLITE_LATEST_SCHEMA_VERSION: + /* DB is already set up. Do nothing */ + ret = 0; + break; + case 0: + /* Query failed -- try to set up new DB */ + ret = sqlite_maindb_init_v1(); + if (ret) + goto out_close; + break; + default: + /* Unknown DB version -- downgrade? Fail */ xlog(L_ERROR, "Unsupported database schema version! " "Expected %d, got %d.", CLTRACK_SQLITE_LATEST_SCHEMA_VERSION, ret); ret = -EINVAL; - goto out_err; + goto out_close; } - /* now create the "clients" table */ - ret = sqlite3_exec(dbh, "CREATE TABLE IF NOT EXISTS clients " - "(id BLOB PRIMARY KEY, time INTEGER);", - NULL, NULL, &err); - if (ret != SQLITE_OK) { - xlog(L_ERROR, "Unable to create clients table: %s", err); - goto out_err; - } - - sqlite3_free(err); - sqlite3_finalize(stmt); - return 0; - -out_err: - if (err) { - xlog(L_ERROR, "sqlite error: %s", err); - sqlite3_free(err); - } - sqlite3_finalize(stmt); - sqlite3_close(dbh); + return ret; +out_close: + sqlite3_close_v2(dbh); + dbh = NULL; return ret; } diff --git a/utils/nfsdcltrack/sqlite.h b/utils/nfsdcltrack/sqlite.h index ebf04c3..3713bfb 100644 --- a/utils/nfsdcltrack/sqlite.h +++ b/utils/nfsdcltrack/sqlite.h @@ -21,7 +21,6 @@ #define _SQLITE_H_ int sqlite_prepare_dbh(const char *topdir); -int sqlite_maindb_init(const char *topdir); int sqlite_insert_client(const unsigned char *clname, const size_t namelen); int sqlite_remove_client(const unsigned char *clname, const size_t namelen); int sqlite_check_client(const unsigned char *clname, const size_t namelen); |