+/**
+ * db4_errfunc - Direct DB errors to logfile
+ *
+ * Basic function to take errors from the DB library and output them to
+ * the logfile rather than stderr.
+ */
+#if (DB_VERSION_MAJOR == 4) && (DB_VERSION_MINOR < 3)
+static void db4_errfunc(const char *errpfx, const char *errmsg)
+#else
+static void db4_errfunc(const DB_ENV *edbenv, const char *errpfx,
+ const char *errmsg)
+#endif
+{
+ if (errpfx) {
+ logthing(LOGTHING_DEBUG, "db4 error: %s:%s", errpfx, errmsg);
+ } else {
+ logthing(LOGTHING_DEBUG, "db4 error: %s", errmsg);
+ }
+
+ return;
+}
+
+/**
+ * starttrans - Start a transaction.
+ *
+ * Start a transaction. Intended to be used if we're about to perform many
+ * operations on the database to help speed it all up, or if we want
+ * something to only succeed if all relevant operations are successful.
+ */
+static bool db4_starttrans(void)
+{
+ int ret;
+
+ log_assert(dbenv != NULL);
+ log_assert(txn == NULL);
+
+ ret = dbenv->txn_begin(dbenv,
+ NULL, /* No parent transaction */
+ &txn,
+ 0);
+ if (ret != 0) {
+ logthing(LOGTHING_CRITICAL,
+ "Error starting transaction: %s",
+ db_strerror(ret));
+ exit(1);
+ }
+
+ return true;
+}
+
+/**
+ * endtrans - End a transaction.
+ *
+ * Ends a transaction.
+ */
+static void db4_endtrans(void)
+{
+ int ret;
+
+ log_assert(dbenv != NULL);
+ log_assert(txn != NULL);
+
+ ret = txn->commit(txn,
+ 0);
+ if (ret != 0) {
+ logthing(LOGTHING_CRITICAL,
+ "Error ending transaction: %s",
+ db_strerror(ret));
+ exit(1);
+ }
+ txn = NULL;
+
+ return;
+}
+
+/**
+ * cleanupdb - De-initialize the key database.
+ *
+ * This function should be called upon program exit to allow the DB to
+ * cleanup after itself.
+ */
+static void db4_cleanupdb(void)
+{
+ int i = 0;
+
+ if (dbenv != NULL) {
+ dbenv->txn_checkpoint(dbenv, 0, 0, 0);
+ if (id32db != NULL) {
+ id32db->close(id32db, 0);
+ id32db = NULL;
+ }
+ if (worddb != NULL) {
+ worddb->close(worddb, 0);
+ worddb = NULL;
+ }
+ for (i = 0; i < numdbs; i++) {
+ if (dbconns[i] != NULL) {
+ dbconns[i]->close(dbconns[i], 0);
+ dbconns[i] = NULL;
+ }
+ }
+ free(dbconns);
+ dbconns = NULL;
+ dbenv->close(dbenv, 0);
+ dbenv = NULL;
+ }
+}
+
+/**
+ * db4_upgradedb - Upgrade a DB4 database
+ *
+ * Called if we discover we need to upgrade our DB4 database; ie if
+ * we're running with a newer version of db4 than the database was
+ * created with.
+ */
+static int db4_upgradedb(int numdb)
+{
+ DB *curdb = NULL;
+ int ret;
+ int i;
+ char buf[1024];
+ int lockfile_fd;
+ struct stat statbuf;
+
+ snprintf(buf, sizeof(buf) - 1, "%s/%s", config.db_dir,
+ DB4_UPGRADE_FILE);
+ lockfile_fd = open(buf, O_RDWR | O_CREAT | O_EXCL, 0600);
+ if (lockfile_fd < 0) {
+ if (errno == EEXIST) {
+ while (stat(buf, &statbuf) == 0) ;
+ return 0;
+ } else {
+ logthing(LOGTHING_CRITICAL, "Couldn't open database "
+ "update lock file: %s", strerror(errno));
+ return -1;
+ }
+ }
+ snprintf(buf, sizeof(buf) - 1, "%d", getpid());
+ write(lockfile_fd, buf, strlen(buf));
+ close(lockfile_fd);
+
+ logthing(LOGTHING_NOTICE, "Upgrading DB4 database");
+ ret = db_env_create(&dbenv, 0);
+ dbenv->set_errcall(dbenv, &db4_errfunc);
+ dbenv->remove(dbenv, config.db_dir, 0);
+ dbenv = NULL;
+ for (i = 0; i < numdb; i++) {
+ ret = db_create(&curdb, NULL, 0);
+ if (ret == 0) {
+ snprintf(buf, sizeof(buf) - 1, "%s/keydb.%d.db",
+ config.db_dir, i);
+ logthing(LOGTHING_DEBUG, "Upgrading %s", buf);
+ ret = curdb->upgrade(curdb, buf, 0);
+ curdb->close(curdb, 0);
+ } else {
+ logthing(LOGTHING_ERROR, "Error upgrading DB %s : %s",
+ buf,
+ db_strerror(ret));
+ }
+ }
+
+ ret = db_create(&curdb, NULL, 0);
+ if (ret == 0) {
+ snprintf(buf, sizeof(buf) - 1, "%s/worddb", config.db_dir);
+ logthing(LOGTHING_DEBUG, "Upgrading %s", buf);
+ ret = curdb->upgrade(curdb, buf, 0);
+ curdb->close(curdb, 0);
+ } else {
+ logthing(LOGTHING_ERROR, "Error upgrading DB %s : %s",
+ buf,
+ db_strerror(ret));
+ }
+
+ ret = db_create(&curdb, NULL, 0);
+ if (ret == 0) {
+ snprintf(buf, sizeof(buf) - 1, "%s/id32db", config.db_dir);
+ logthing(LOGTHING_DEBUG, "Upgrading %s", buf);
+ ret = curdb->upgrade(curdb, buf, 0);
+ curdb->close(curdb, 0);
+ } else {
+ logthing(LOGTHING_ERROR, "Error upgrading DB %s : %s",
+ buf,
+ db_strerror(ret));
+ }
+
+ snprintf(buf, sizeof(buf) - 1, "%s/%s", config.db_dir,
+ DB4_UPGRADE_FILE);
+ unlink(buf);
+
+ return ret;
+}
+