After a failed kdb5_util load, make a subsequent load operation work
authorGreg Hudson <ghudson@mit.edu>
Sat, 6 Nov 2010 00:02:13 +0000 (00:02 +0000)
committerGreg Hudson <ghudson@mit.edu>
Sat, 6 Nov 2010 00:02:13 +0000 (00:02 +0000)
by removing the remnant temporary files after obtaining a lock.  To
make this safe, the private contract for temporary DB creation and
promotion had to be altered, along with many of the DB2 internal
helper functions.

ticket: 6814

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24511 dc483132-0cff-0310-8789-dd5450dbe970

src/include/kdb.h
src/kadmin/dbutil/dump.c
src/plugins/kdb/db2/kdb_db2.c
src/plugins/kdb/db2/kdb_db2.h

index 45622791776f13702ccc6b024e12950e7f4cabb1..ed1961ea252a11700e3f49835d34a3e5badee74d 100644 (file)
@@ -800,14 +800,6 @@ typedef struct _kdb_vftabl {
      * command-line arguments for module-specific flags.  mode will be one of
      * KRB5_KDB_OPEN_{RW,RO} or'd with one of
      * KRB5_KDB_SRV_TYPE_{KDC,ADMIN,PASSWD,OTHER}.
-     *
-     * A db_args value of "temporary" is generated programattically by
-     * kdb5_util load.  If this db_args value is present, the module should
-     * open a side copy of the database suitable for loading in a propagation
-     * from master to slave.  This side copy will later be promoted with
-     * promote_db, allowing complete updates of the DB with no loss in read
-     * availability.  If the module cannot comply with this architecture, it
-     * should return an error.
      */
     krb5_error_code (*init_module)(krb5_context kcontext, char *conf_section,
                                    char **db_args, int mode);
@@ -823,6 +815,13 @@ typedef struct _kdb_vftabl {
      * database.  conf_section and db_args have the same meaning as in
      * init_module.  This function may return an error if the database already
      * exists.  Used by kdb5_util create.
+     *
+     * If db_args contains the value "temporary", the module should create an
+     * exclusively locked side copy of the database suitable for loading in a
+     * propagation from master to slave.  This side copy will later be promoted
+     * with promote_db, allowing complete updates of the DB with no loss in
+     * read availability.  If the module cannot comply with this architecture,
+     * it should return an error.
      */
     krb5_error_code (*create)(krb5_context kcontext, char *conf_section,
                               char **db_args);
@@ -1104,10 +1103,13 @@ typedef struct _kdb_vftabl {
                                   krb5_db_entry *db_entry);
 
     /*
-     * Optional: Promote a temporary database to be the live one.  kdb5_util
-     * load opens the database with the "temporary" db_arg and then invokes
-     * this function when the load is complete, thus replacing the live
-     * database with no loss of read availability.
+     * Optional: Promote a temporary database to be the live one.  context must
+     * be initialized with an exclusively locked database created with the
+     * "temporary" db_arg.  On success, the database object contained in
+     * context will be finalized.
+     *
+     * This method is used by kdb5_util load to replace the live database with
+     * minimal loss of read availability.
      */
     krb5_error_code (*promote_db)(krb5_context context, char *conf_section,
                                   char **db_args);
index 0e1b60959412e8a3a18d8d6d6f0dd72992dc19d7..96d1a13c4786cf5c8a144fac8ef05f53543557c1 100644 (file)
@@ -2522,41 +2522,29 @@ load_db(argc, argv)
         }
     }
     else {
-        /*
-         * Initialize the database.
-         */
-        if ((kret = krb5_db_open(kcontext, db5util_db_args,
-                                 KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN))) {
+        /* Initialize the database. */
+        kret = krb5_db_open(kcontext, db5util_db_args,
+                            KRB5_KDB_OPEN_RW | KRB5_KDB_SRV_TYPE_ADMIN);
+        if (kret) {
             const char *emsg = krb5_get_error_message(kcontext, kret);
             fprintf(stderr, "%s: %s\n", progname, emsg);
             krb5_free_error_message (kcontext, emsg);
             exit_status++;
             goto error;
         }
-    }
 
-
-    /*
-     * If an update restoration, make sure the db is left unusable if
-     * the update fails.
-     */
-    if ((kret = krb5_db_lock(kcontext,
-                             (flags & FLAG_UPDATE) ?
-                             KRB5_DB_LOCKMODE_PERMANENT :
-                             KRB5_DB_LOCKMODE_EXCLUSIVE))) {
-        /*
-         * Ignore a not supported error since there is nothing to do about it
-         * anyway.
-         */
-        if (kret != KRB5_PLUGIN_OP_NOTSUPP) {
+        /* Make sure the db is left unusable if the update fails, if the db
+         * supports locking. */
+        kret = krb5_db_lock(kcontext, KRB5_DB_LOCKMODE_PERMANENT);
+        if (kret == 0)
+            db_locked = 1;
+        else if (kret != KRB5_PLUGIN_OP_NOTSUPP) {
             fprintf(stderr, "%s: %s while permanently locking database\n",
                     progname, error_message(kret));
             exit_status++;
             goto error;
         }
     }
-    else
-        db_locked = 1;
 
     if (log_ctx && log_ctx->iproprole) {
         if (add_update)
index e1a7a10a20ee016eaefbad616471c8d0b79846ed..d7baca93a0f920d7a123e97846deb7f58cea187a 100644 (file)
 
 #define KDB_DB2_DATABASE_NAME "database_name"
 
+#define SUFFIX_DB ""
+#define SUFFIX_LOCK ".ok"
+#define SUFFIX_POLICY ".kadm5"
+#define SUFFIX_POLICY_LOCK ".kadm5.lock"
+
 /*
  * Locking:
  *
@@ -147,44 +152,47 @@ get_db_opt(char *input, char **opt, char **val)
 
 /* Restore dbctx to the uninitialized state. */
 static void
-clear_context(krb5_db2_context *dbctx)
+ctx_clear(krb5_db2_context *dbc)
 {
     /*
      * Free any dynamically allocated memory.  File descriptors and locks
      * are the caller's problem.
      */
-    free(dbctx->db_lf_name);
-    free(dbctx->db_name);
+    free(dbc->db_lf_name);
+    free(dbc->db_name);
     /*
      * Clear the structure and reset the defaults.
      */
-    memset(dbctx, 0, sizeof(krb5_db2_context));
-    dbctx->db_name = NULL;
-    dbctx->db_nb_locks = FALSE;
-    dbctx->tempdb = FALSE;
+    memset(dbc, 0, sizeof(krb5_db2_context));
+    dbc->db = NULL;
+    dbc->db_lf_name = NULL;
+    dbc->db_lf_file = -1;
+    dbc->db_name = NULL;
+    dbc->db_nb_locks = FALSE;
+    dbc->tempdb = FALSE;
 }
 
-/* Set *db_ctx_out to the db2 database context for context.  If one does
- * not exist, create one in the uninitialized state. */
+/* Set *dbc_out to the db2 database context for context.  If one does not
+ * exist, create one in the uninitialized state. */
 static krb5_error_code
-get_context(krb5_context context, krb5_db2_context **db_ctx_out)
+ctx_get(krb5_context context, krb5_db2_context **dbc_out)
 {
-    krb5_db2_context *db_ctx;
+    krb5_db2_context *dbc;
     kdb5_dal_handle *dal_handle;
 
     dal_handle = context->dal_handle;
 
     if (dal_handle->db_context == NULL) {
-        db_ctx = (krb5_db2_context *) malloc(sizeof(krb5_db2_context));
-        if (db_ctx == NULL)
+        dbc = (krb5_db2_context *) malloc(sizeof(krb5_db2_context));
+        if (dbc == NULL)
             return ENOMEM;
         else {
-            memset(db_ctx, 0, sizeof(krb5_db2_context));
-            clear_context(db_ctx);
-            dal_handle->db_context = db_ctx;
+            memset(dbc, 0, sizeof(krb5_db2_context));
+            ctx_clear(dbc);
+            dal_handle->db_context = dbc;
         }
     }
-    *db_ctx_out = dal_handle->db_context;
+    *dbc_out = dal_handle->db_context;
     return 0;
 }
 
@@ -194,12 +202,12 @@ static krb5_error_code
 configure_context(krb5_context context, char *conf_section, char **db_args)
 {
     krb5_error_code status;
-    krb5_db2_context *db_ctx;
+    krb5_db2_context *dbc;
     char **t_ptr, *opt = NULL, *val = NULL, *pval = NULL;
     profile_t profile = KRB5_DB_GET_PROFILE(context);
     int bval;
 
-    status = get_context(context, &db_ctx);
+    status = ctx_get(context, &dbc);
     if (status != 0)
         return status;
 
@@ -208,18 +216,18 @@ configure_context(krb5_context context, char *conf_section, char **db_args)
         free(val);
         status = get_db_opt(*t_ptr, &opt, &val);
         if (opt && !strcmp(opt, "dbname")) {
-            db_ctx->db_name = strdup(val);
-            if (db_ctx->db_name == NULL) {
+            dbc->db_name = strdup(val);
+            if (dbc->db_name == NULL) {
                 status = ENOMEM;
                 goto cleanup;
             }
         }
         else if (!opt && !strcmp(val, "temporary")) {
-            db_ctx->tempdb = 1;
+            dbc->tempdb = 1;
         } else if (!opt && !strcmp(val, "merge_nra")) {
             ;
         } else if (opt && !strcmp(opt, "hash")) {
-            db_ctx->hashfirst = TRUE;
+            dbc->hashfirst = TRUE;
         } else {
             status = EINVAL;
             krb5_set_error_message(context, status,
@@ -229,7 +237,7 @@ configure_context(krb5_context context, char *conf_section, char **db_args)
         }
     }
 
-    if (db_ctx->db_name == NULL) {
+    if (dbc->db_name == NULL) {
         /* Check for database_name in the db_module section. */
         status = profile_get_string(profile, KDB_MODULE_SECTION, conf_section,
                                     KDB_DB2_DATABASE_NAME, NULL, &pval);
@@ -242,20 +250,20 @@ configure_context(krb5_context context, char *conf_section, char **db_args)
         }
         if (status != 0)
             goto cleanup;
-        db_ctx->db_name = strdup(pval);
+        dbc->db_name = strdup(pval);
     }
 
     status = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,
                                  KRB5_CONF_DISABLE_LAST_SUCCESS, FALSE, &bval);
     if (status != 0)
         goto cleanup;
-    db_ctx->disable_last_success = bval;
+    dbc->disable_last_success = bval;
 
     status = profile_get_boolean(profile, KDB_MODULE_SECTION, conf_section,
                                  KRB5_CONF_DISABLE_LOCKOUT, FALSE, &bval);
     if (status != 0)
         goto cleanup;
-    db_ctx->disable_lockout = bval;
+    dbc->disable_lockout = bval;
 
 cleanup:
     free(opt);
@@ -264,29 +272,64 @@ cleanup:
     return status;
 }
 
-/* Set *out to the concatenation of db_name and sfx. */
+/*
+ * Set *out to one of the filenames used for the DB described by dbc.  sfx
+ * should be one of SUFFIX_DB, SUFFIX_LOCK, SUFFIX_POLICY, or
+ * SUFFIX_POLICY_LOCK.
+ */
 static krb5_error_code
-gen_dbsuffix(char *db_name, char *sfx, char **out)
+ctx_dbsuffix(krb5_db2_context *dbc, const char *sfx, char **out)
 {
     char *result;
+    const char *tilde;
 
     *out = NULL;
-    if (asprintf(&result, "%s%s", db_name, sfx) < 0)
+    tilde = dbc->tempdb ? "~" : "";
+    if (asprintf(&result, "%s%s%s", dbc->db_name, tilde, sfx) < 0)
         return ENOMEM;
     *out = result;
     return 0;
 }
 
+/* Generate all four files corresponding to dbc. */
+static krb5_error_code
+ctx_allfiles(krb5_db2_context *dbc, char **dbname_out, char **lockname_out,
+             char **polname_out, char **plockname_out)
+{
+    char *a = NULL, *b = NULL, *c = NULL, *d = NULL;
+
+    *dbname_out = *lockname_out = *polname_out = *plockname_out = NULL;
+    if (ctx_dbsuffix(dbc, SUFFIX_DB, &a))
+        goto error;
+    if (ctx_dbsuffix(dbc, SUFFIX_LOCK, &b))
+        goto error;
+    if (ctx_dbsuffix(dbc, SUFFIX_POLICY, &c))
+        goto error;
+    if (ctx_dbsuffix(dbc, SUFFIX_POLICY_LOCK, &d))
+        goto error;
+    *dbname_out = a;
+    *lockname_out = b;
+    *polname_out = c;
+    *plockname_out = d;
+    return 0;
+error:
+    free(a);
+    free(b);
+    free(c);
+    free(d);
+    return ENOMEM;
+}
+
 /*
- * Open the DB2 database given by fname (possibly modified by dbc->tempdb),
- * using the specified flags and mode, and return the resulting handle.  Try
- * both hash and btree database types; dbc->hashfirst determines which is
- * attempted first.  If dbc->hashfirst indicated the wrong type, update it to
- * indicate the correct type.
+ * Open the DB2 database described by dbc, using the specified flags and mode,
+ * and return the resulting handle.  Try both hash and btree database types;
+ * dbc->hashfirst determines which is attempted first.  If dbc->hashfirst
+ * indicated the wrong type, update it to indicate the correct type.
  */
 static DB *
-open_db(krb5_db2_context *dbc, char *fname, int flags, int mode)
+open_db(krb5_db2_context *dbc, int flags, int mode)
 {
+    char *fname = NULL;
     DB *db;
     BTREEINFO bti;
     HASHINFO hashi;
@@ -298,7 +341,7 @@ open_db(krb5_db2_context *dbc, char *fname, int flags, int mode)
     bti.compare = NULL;
     bti.prefix = NULL;
 
-    if (gen_dbsuffix(fname, dbc->tempdb ? "~" : "", &fname) != 0) {
+    if (ctx_dbsuffix(dbc, SUFFIX_DB, &fname) != 0) {
         errno = ENOMEM;
         return NULL;
     }
@@ -337,108 +380,168 @@ done:
     return db;
 }
 
-/* Initialize the lock file and policy database fields of the DB2 context
- * within context.  The db_name and tempdb fields must already be set. */
 static krb5_error_code
-init_db2_context(krb5_context context)
+ctx_unlock(krb5_context context, krb5_db2_context *dbc)
 {
-    char *filename = NULL;
-    krb5_db2_context *db_ctx;
     krb5_error_code retval;
-    char policy_db_name[1024], policy_lock_name[1024];
-
-    if (inited(context))
-        return 0;
+    DB *db;
 
-    retval = get_context(context, &db_ctx);
+    retval = osa_adb_release_lock(dbc->policy_db);
     if (retval)
         return retval;
 
-    db_ctx->db = NULL;
+    if (!dbc->db_locks_held) /* lock already unlocked */
+        return KRB5_KDB_NOTLOCKED;
+
+    db = dbc->db;
+    if (--(dbc->db_locks_held) == 0) {
+        db->close(db);
+        dbc->db = NULL;
+        dbc->db_lock_mode = 0;
+
+        retval = krb5_lock_file(context, dbc->db_lf_file,
+                                KRB5_LOCKMODE_UNLOCK);
+    }
+    return retval;
+}
+
+#define MAX_LOCK_TRIES 5
+
+static krb5_error_code
+ctx_lock(krb5_context context, krb5_db2_context *dbc, int lockmode)
+{
+    krb5_error_code retval;
+    int kmode, tries;
+
+    if (lockmode == KRB5_DB_LOCKMODE_PERMANENT ||
+        lockmode == KRB5_DB_LOCKMODE_EXCLUSIVE)
+        kmode = KRB5_LOCKMODE_EXCLUSIVE;
+    else if (lockmode == KRB5_DB_LOCKMODE_SHARED)
+        kmode = KRB5_LOCKMODE_SHARED;
+    else
+        return EINVAL;
+
+    if (dbc->db_locks_held == 0 || dbc->db_lock_mode < kmode) {
+        /* Acquire or upgrade the lock. */
+        for (tries = 0; tries < MAX_LOCK_TRIES; tries++) {
+            retval = krb5_lock_file(context, dbc->db_lf_file,
+                                    kmode | KRB5_LOCKMODE_DONTBLOCK);
+            if (retval == 0)
+                break;
+            if (retval == EBADF && kmode == KRB5_LOCKMODE_EXCLUSIVE)
+                /* Tried to lock something we don't have write access to. */
+                return KRB5_KDB_CANTLOCK_DB;
+            sleep(1);
+        }
+        if (retval == EACCES)
+            return KRB5_KDB_CANTLOCK_DB;
+        else if (retval == EAGAIN || retval == EWOULDBLOCK)
+            return OSA_ADB_CANTLOCK_DB;
+        else if (retval)
+            return retval;
+
+        /* Open the DB (or re-open it for read/write). */
+        if (dbc->db != NULL)
+            dbc->db->close(dbc->db);
+        dbc->db = open_db(dbc,
+                          kmode == KRB5_LOCKMODE_SHARED ? O_RDONLY : O_RDWR,
+                          0600);
+        if (dbc->db == NULL) {
+            retval = errno;
+            dbc->db_locks_held = 0;
+            dbc->db_lock_mode = 0;
+            (void) osa_adb_release_lock(dbc->policy_db);
+            (void) krb5_lock_file(context, dbc->db_lf_file,
+                                  KRB5_LOCKMODE_UNLOCK);
+            return retval;
+        }
+
+        dbc->db_lock_mode = kmode;
+    }
+    dbc->db_locks_held++;
+
+    /* Acquire or upgrade the policy lock. */
+    retval = osa_adb_get_lock(dbc->policy_db, lockmode);
+    if (retval)
+        (void) ctx_unlock(context, dbc);
+    return retval;
+}
+
+/* Initialize the lock file and policy database fields of dbc.  The db_name and
+ * tempdb fields must already be set. */
+static krb5_error_code
+ctx_init(krb5_db2_context *dbc)
+{
+    krb5_error_code retval;
+    char *polname = NULL, *plockname = NULL;
 
-    retval = gen_dbsuffix(db_ctx->db_name, db_ctx->tempdb ?
-                          KDB2_TEMP_LOCK_EXT : KDB2_LOCK_EXT, &filename);
+    retval = ctx_dbsuffix(dbc, SUFFIX_LOCK, &dbc->db_lf_name);
     if (retval)
         return retval;
-    db_ctx->db_lf_name = filename;      /* so it gets freed by clear_context */
 
     /*
      * should be opened read/write so that write locking can work with
      * POSIX systems
      */
-    if ((db_ctx->db_lf_file = open(filename, O_RDWR, 0666)) < 0) {
-        if ((db_ctx->db_lf_file = open(filename, O_RDONLY, 0666)) < 0) {
+    if ((dbc->db_lf_file = open(dbc->db_lf_name, O_RDWR, 0666)) < 0) {
+        if ((dbc->db_lf_file = open(dbc->db_lf_name, O_RDONLY, 0666)) < 0) {
             retval = errno;
-            goto err_out;
+            goto cleanup;
         }
     }
-    set_cloexec_fd(db_ctx->db_lf_file);
-    db_ctx->db_inited++;
-
-    snprintf(policy_db_name, sizeof(policy_db_name), "%s%s.kadm5",
-             db_ctx->db_name, db_ctx->tempdb ? "~" : "");
-    snprintf(policy_lock_name, sizeof(policy_lock_name),
-             "%s.lock", policy_db_name);
-
-    if ((retval = osa_adb_init_db(&db_ctx->policy_db, policy_db_name,
-                                  policy_lock_name, OSA_ADB_POLICY_DB_MAGIC)))
-    {
-        goto err_out;
-    }
-    return 0;
+    set_cloexec_fd(dbc->db_lf_file);
+    dbc->db_inited++;
 
-err_out:
-    db_ctx->db = NULL;
-    clear_context(db_ctx);
-    return (retval);
+    retval = ctx_dbsuffix(dbc, SUFFIX_POLICY, &polname);
+    if (retval)
+        goto cleanup;
+    retval = ctx_dbsuffix(dbc, SUFFIX_POLICY_LOCK, &plockname);
+    if (retval)
+        goto cleanup;
+    retval = osa_adb_init_db(&dbc->policy_db, polname, plockname,
+                             OSA_ADB_POLICY_DB_MAGIC);
+
+cleanup:
+    free(polname);
+    free(plockname);
+    if (retval)
+        ctx_clear(dbc);
+    return retval;
+}
+
+static void
+ctx_fini(krb5_db2_context *dbc)
+{
+    if (dbc->db_lf_file != -1)
+        (void) close(dbc->db_lf_file);
+    if (dbc->policy_db)
+        (void) osa_adb_fini_db(dbc->policy_db, OSA_ADB_POLICY_DB_MAGIC);
+    ctx_clear(dbc);
+    free(dbc);
 }
 
-/*
- * gracefully shut down database--must be called by ANY program that does
- * a krb5_db2_init
- */
 krb5_error_code
 krb5_db2_fini(krb5_context context)
 {
-    krb5_error_code retval = 0;
-    krb5_db2_context *db_ctx;
-
-    db_ctx = context->dal_handle->db_context;
-    if (inited(context)) {
-        if (close(db_ctx->db_lf_file))
-            retval = errno;
-        else
-            retval = 0;
-    }
-    if (db_ctx) {
-        if (db_ctx->policy_db) {
-            retval =
-                osa_adb_fini_db(db_ctx->policy_db, OSA_ADB_POLICY_DB_MAGIC);
-            if (retval)
-                return retval;
-        }
-
-        clear_context(db_ctx);
-        free(context->dal_handle->db_context);
+    if (context->dal_handle->db_context != NULL) {
+        ctx_fini(context->dal_handle->db_context);
         context->dal_handle->db_context = NULL;
     }
-    return retval;
+    return 0;
 }
 
-
-
 /* Return successfully if the db2 name set in context can be opened. */
 static krb5_error_code
 check_openable(krb5_context context)
 {
     DB     *db;
-    krb5_db2_context *db_ctx;
+    krb5_db2_context *dbc;
 
-    db_ctx = context->dal_handle->db_context;
-    db = open_db(db_ctx, db_ctx->db_name, O_RDONLY, 0);
+    dbc = context->dal_handle->db_context;
+    db = open_db(dbc, O_RDONLY, 0);
     if (db == NULL)
         return errno;
-    (*db->close) (db);
+    db->close(db);
     return 0;
 }
 
@@ -451,254 +554,73 @@ check_openable(krb5_context context)
 krb5_error_code
 krb5_db2_get_age(krb5_context context, char *db_name, time_t *age)
 {
-    krb5_db2_context *db_ctx;
+    krb5_db2_context *dbc;
     struct stat st;
 
     if (!inited(context))
         return (KRB5_KDB_DBNOTINITED);
-    db_ctx = context->dal_handle->db_context;
+    dbc = context->dal_handle->db_context;
 
-    if (fstat(db_ctx->db_lf_file, &st) < 0)
+    if (fstat(dbc->db_lf_file, &st) < 0)
         *age = -1;
     else
         *age = st.st_mtime;
     return 0;
 }
 
-/*
- * Remove the semaphore file; indicates that database is currently
- * under renovation.
- *
- * This is only for use when moving the database out from underneath
- * the server (for example, during slave updates).
- */
-
-static krb5_error_code
-start_update(krb5_context context)
-{
-    return 0;
-}
-
-static krb5_error_code
-end_update(krb5_context context)
+/* Try to update the timestamp on dbc's lockfile. */
+static void
+ctx_update_age(krb5_db2_context *dbc)
 {
-    krb5_error_code retval;
-    krb5_db2_context *db_ctx;
     struct stat st;
-    time_t  now;
+    time_t now;
     struct utimbuf utbuf;
 
-    if (!inited(context))
-        return (KRB5_KDB_DBNOTINITED);
-
-    retval = 0;
-    db_ctx = context->dal_handle->db_context;
     now = time((time_t *) NULL);
-    if (fstat(db_ctx->db_lf_file, &st) == 0) {
-        if (st.st_mtime >= now) {
-            utbuf.actime = st.st_mtime + 1;
-            utbuf.modtime = st.st_mtime + 1;
-            if (utime(db_ctx->db_lf_name, &utbuf))
-                retval = errno;
-        } else {
-            if (utime(db_ctx->db_lf_name, (struct utimbuf *) NULL))
-                retval = errno;
-        }
+    if (fstat(dbc->db_lf_file, &st) != 0)
+        return;
+    if (st.st_mtime >= now) {
+        utbuf.actime = st.st_mtime + 1;
+        utbuf.modtime = st.st_mtime + 1;
+        (void) utime(dbc->db_lf_name, &utbuf);
     } else
-        retval = errno;
-    return retval;
+        (void) utime(dbc->db_lf_name, (struct utimbuf *) NULL);
 }
 
-#define MAX_LOCK_TRIES 5
-
 krb5_error_code
-krb5_db2_lock(krb5_context context, int in_mode)
+krb5_db2_lock(krb5_context context, int lockmode)
 {
-    krb5_db2_context *db_ctx;
-    int     krb5_lock_mode;
-    krb5_error_code retval;
-    time_t  mod_time;
-    int     mode, gotlock, tries;
-
-    switch (in_mode) {
-    case KRB5_DB_LOCKMODE_PERMANENT:
-        mode = KRB5_DB_LOCKMODE_EXCLUSIVE;
-        break;
-    case KRB5_DB_LOCKMODE_EXCLUSIVE:
-        mode = KRB5_LOCKMODE_EXCLUSIVE;
-        break;
-
-    case KRB5_DB_LOCKMODE_SHARED:
-        mode = KRB5_LOCKMODE_SHARED;
-        break;
-    default:
-        return EINVAL;
-    }
-
     if (!inited(context))
         return KRB5_KDB_DBNOTINITED;
-
-    db_ctx = context->dal_handle->db_context;
-    if (db_ctx->db_locks_held && (db_ctx->db_lock_mode >= mode)) {
-        /* No need to upgrade lock, just return */
-        db_ctx->db_locks_held++;
-        goto policy_lock;
-    }
-
-    if ((mode != KRB5_LOCKMODE_SHARED) && (mode != KRB5_LOCKMODE_EXCLUSIVE))
-        return KRB5_KDB_BADLOCKMODE;
-
-    krb5_lock_mode = mode | KRB5_LOCKMODE_DONTBLOCK;
-    for (gotlock = tries = 0; tries < MAX_LOCK_TRIES; tries++) {
-        retval = krb5_lock_file(context, db_ctx->db_lf_file, krb5_lock_mode);
-        if (retval == 0) {
-            gotlock++;
-            break;
-        } else if (retval == EBADF && mode == KRB5_DB_LOCKMODE_EXCLUSIVE)
-            /* tried to exclusive-lock something we don't have */
-            /* write access to */
-            return KRB5_KDB_CANTLOCK_DB;
-        sleep(1);
-    }
-    if (retval == EACCES)
-        return KRB5_KDB_CANTLOCK_DB;
-    else if (retval == EAGAIN || retval == EWOULDBLOCK)
-        return OSA_ADB_CANTLOCK_DB;
-    else if (retval != 0)
-        return retval;
-
-    if ((retval = krb5_db2_get_age(context, NULL, &mod_time)))
-        goto lock_error;
-
-    db_ctx->db = open_db(db_ctx, db_ctx->db_name,
-                         mode == KRB5_LOCKMODE_SHARED ? O_RDONLY : O_RDWR,
-                         0600);
-    if (db_ctx->db == NULL) {
-        retval = errno;
-        goto lock_error;
-    }
-
-    db_ctx->db_lock_mode = mode;
-    db_ctx->db_locks_held++;
-
-policy_lock:
-    if ((retval = osa_adb_get_lock(db_ctx->policy_db, in_mode))) {
-        krb5_db2_unlock(context);
-    }
-    return retval;
-
-lock_error:;
-    db_ctx->db_lock_mode = 0;
-    db_ctx->db_locks_held = 0;
-    krb5_db2_unlock(context);
-    return retval;
+    return ctx_lock(context, context->dal_handle->db_context, lockmode);
 }
 
 krb5_error_code
 krb5_db2_unlock(krb5_context context)
 {
-    krb5_db2_context *db_ctx;
-    DB     *db;
-    krb5_error_code retval;
-
     if (!inited(context))
         return KRB5_KDB_DBNOTINITED;
-
-    db_ctx = context->dal_handle->db_context;
-
-    if ((retval = osa_adb_release_lock(db_ctx->policy_db))) {
-        return retval;
-    }
-
-    if (!db_ctx->db_locks_held) /* lock already unlocked */
-        return KRB5_KDB_NOTLOCKED;
-    db = db_ctx->db;
-    if (--(db_ctx->db_locks_held) == 0) {
-        (*db->close) (db);
-        db_ctx->db = NULL;
-
-        retval = krb5_lock_file(context, db_ctx->db_lf_file,
-                                KRB5_LOCKMODE_UNLOCK);
-        db_ctx->db_lock_mode = 0;
-        return (retval);
-    }
-    return 0;
+    return ctx_unlock(context, context->dal_handle->db_context);
 }
 
-/* Create the database, assuming it's not there. */
+/* Zero out and unlink filename. */
 static krb5_error_code
-create_db(krb5_context context, char *db_name)
+destroy_file(char *filename)
 {
-    krb5_error_code retval = 0;
-    char   *okname;
-    char   *db_name2 = NULL;
-    int     fd;
-    krb5_db2_context *db_ctx;
-    DB     *db;
-    char    policy_db_name[1024], policy_lock_name[1024];
-
-    retval = get_context(context, &db_ctx);
-    if (retval != 0)
-        return retval;
-
-    db = open_db(db_ctx, db_name, O_RDWR | O_CREAT | O_EXCL, 0600);
-    if (db == NULL)
-        return errno;
-    (*db->close)(db);
-
-    retval = gen_dbsuffix(db_name, db_ctx->tempdb ? "~" : "", &db_name2);
-    if (retval)
-        return retval;
-    retval = gen_dbsuffix(db_name2, KDB2_LOCK_EXT, &okname);
-    if (retval == 0) {
-        fd = open(okname, O_CREAT | O_RDWR | O_TRUNC, 0600);
-        if (fd < 0)
-            retval = errno;
-        else
-            close(fd);
-        free(okname);
-    }
-
-    snprintf(policy_db_name, sizeof(policy_db_name), "%s.kadm5", db_name2);
-    snprintf(policy_lock_name, sizeof(policy_lock_name),
-             "%s.lock", policy_db_name);
-
-    retval = osa_adb_create_db(policy_db_name,
-                               policy_lock_name, OSA_ADB_POLICY_DB_MAGIC);
-    free(db_name2);
-    return retval;
-}
-
-/*
- * Destroy the database.  Zero's out all of the files, just to be sure.
- */
-static krb5_error_code
-destroy_file_suffix(char *dbname, char *suffix)
-{
-    char   *filename;
     struct stat statb;
-    int     nb, fd;
-    int     j;
-    off_t   pos;
-    char    buf[BUFSIZ];
-    char    zbuf[BUFSIZ];
-    int     dowrite;
-
-    if (gen_dbsuffix(dbname, suffix, &filename) != 0)
-        return ENOMEM;
-    if ((fd = open(filename, O_RDWR, 0)) < 0) {
-        free(filename);
+    int dowrite, j, nb, fd, retval;
+    off_t pos;
+    char buf[BUFSIZ], zbuf[BUFSIZ];
+
+    fd = open(filename, O_RDWR, 0);
+    if (fd < 0)
         return errno;
-    }
     set_cloexec_fd(fd);
     /* fstat() will probably not fail unless using a remote filesystem
      * (which is inappropriate for the kerberos database) so this check
      * is mostly paranoia.  */
-    if (fstat(fd, &statb) == -1) {
-        int     retval = errno;
-        free(filename);
-        return retval;
-    }
+    if (fstat(fd, &statb) == -1)
+        goto error;
     /*
      * Stroll through the file, reading in BUFSIZ chunks.  If everything
      * is zero, then we're done for that block, otherwise, zero the block.
@@ -712,11 +634,8 @@ destroy_file_suffix(char *dbname, char *suffix)
     while (pos < statb.st_size) {
         dowrite = 0;
         nb = read(fd, buf, BUFSIZ);
-        if (nb < 0) {
-            int     retval = errno;
-            free(filename);
-            return retval;
-        }
+        if (nb < 0)
+            goto error;
         for (j = 0; j < nb; j++) {
             if (buf[j] != '\0') {
                 dowrite = 1;
@@ -728,11 +647,8 @@ destroy_file_suffix(char *dbname, char *suffix)
         if (dowrite) {
             lseek(fd, pos, SEEK_SET);
             nb = write(fd, zbuf, j);
-            if (nb < 0) {
-                int     retval = errno;
-                free(filename);
-                return retval;
-            }
+            if (nb < 0)
+                goto error;
         }
         pos += nb;
     }
@@ -744,19 +660,94 @@ destroy_file_suffix(char *dbname, char *suffix)
 #endif
     close(fd);
 
-    if (unlink(filename)) {
-        free(filename);
-        return (errno);
+    if (unlink(filename))
+        return errno;
+    return 0;
+
+error:
+    retval = errno;
+    close(fd);
+    return retval;
+}
+
+/* Initialize dbc by locking and creating the DB.  If the DB already exists,
+ * clear it out if dbc->tempdb is set; otherwise return EEXIST. */
+static krb5_error_code
+ctx_create_db(krb5_context context, krb5_db2_context *dbc)
+{
+    krb5_error_code retval = 0;
+    char *dbname = NULL, *polname = NULL, *plockname = NULL;
+
+    retval = ctx_allfiles(dbc, &dbname, &dbc->db_lf_name, &polname,
+                          &plockname);
+    if (retval)
+        return retval;
+
+    dbc->db_lf_file = open(dbc->db_lf_name, O_CREAT | O_RDWR | O_TRUNC,
+                              0600);
+    if (dbc->db_lf_file < 0) {
+        retval = errno;
+        goto cleanup;
     }
-    free(filename);
-    return (0);
+    retval = krb5_lock_file(context, dbc->db_lf_file,
+                            KRB5_LOCKMODE_EXCLUSIVE | KRB5_LOCKMODE_DONTBLOCK);
+    if (retval != 0)
+        goto cleanup;
+    set_cloexec_fd(dbc->db_lf_file);
+    dbc->db_lock_mode = KRB5_LOCKMODE_EXCLUSIVE;
+    dbc->db_locks_held = 1;
+
+    if (dbc->tempdb) {
+        /* Temporary DBs are locked for their whole lifetime.  Since we have
+         * the lock, any remnant files can be safely destroyed. */
+        (void) destroy_file(dbname);
+        (void) unlink(polname);
+        (void) unlink(plockname);
+    }
+
+    dbc->db = open_db(dbc, O_RDWR | O_CREAT | O_EXCL, 0600);
+    if (dbc->db == NULL) {
+        retval = errno;
+        goto cleanup;
+    }
+
+    /* Create the policy database, initialize a handle to it, and lock it. */
+    retval = osa_adb_create_db(polname, plockname, OSA_ADB_POLICY_DB_MAGIC);
+    if (retval)
+        goto cleanup;
+    retval = osa_adb_init_db(&dbc->policy_db, polname, plockname,
+                             OSA_ADB_POLICY_DB_MAGIC);
+    if (retval)
+        goto cleanup;
+    retval = osa_adb_get_lock(dbc->policy_db, KRB5_DB_LOCKMODE_EXCLUSIVE);
+    if (retval)
+        goto cleanup;
+
+    dbc->db_inited = 1;
+
+cleanup:
+    if (retval) {
+        if (dbc->db != NULL)
+            dbc->db->close(dbc->db);
+        if (dbc->db_locks_held > 0) {
+            (void) krb5_lock_file(context, dbc->db_lf_file,
+                                  KRB5_LOCKMODE_UNLOCK);
+        }
+        if (dbc->db_lf_file >= 0)
+            close(dbc->db_lf_file);
+        ctx_clear(dbc);
+    }
+    free(dbname);
+    free(polname);
+    free(plockname);
+    return retval;
 }
 
 krb5_error_code
 krb5_db2_get_principal(krb5_context context, krb5_const_principal searchfor,
                        unsigned int flags, krb5_db_entry **entry)
 {
-    krb5_db2_context *db_ctx;
+    krb5_db2_context *dbc;
     krb5_error_code retval;
     DB     *db;
     DBT     key, contents;
@@ -767,11 +758,11 @@ krb5_db2_get_principal(krb5_context context, krb5_const_principal searchfor,
     if (!inited(context))
         return KRB5_KDB_DBNOTINITED;
 
-    db_ctx = context->dal_handle->db_context;
+    dbc = context->dal_handle->db_context;
 
     for (trynum = 0; trynum < KRB5_DB2_MAX_RETRY; trynum++) {
-        if ((retval = krb5_db2_lock(context, KRB5_LOCKMODE_SHARED))) {
-            if (db_ctx->db_nb_locks)
+        if ((retval = ctx_lock(context, dbc, KRB5_LOCKMODE_SHARED))) {
+            if (dbc->db_nb_locks)
                 return (retval);
             sleep(1);
             continue;
@@ -788,7 +779,7 @@ krb5_db2_get_principal(krb5_context context, krb5_const_principal searchfor,
     key.data = keydata.data;
     key.size = keydata.length;
 
-    db = db_ctx->db;
+    db = dbc->db;
     dbret = (*db->get)(db, &key, &contents, 0);
     retval = errno;
     krb5_free_data_contents(context, &keydata);
@@ -827,7 +818,7 @@ krb5_db2_put_principal(krb5_context context, krb5_db_entry *entry,
     DBT     key, contents;
     krb5_data contdata, keydata;
     krb5_error_code retval;
-    krb5_db2_context *db_ctx;
+    krb5_db2_context *dbc;
 
     krb5_clear_error_message (context);
     if (db_args) {
@@ -841,15 +832,11 @@ krb5_db2_put_principal(krb5_context context, krb5_db_entry *entry,
     if (!inited(context))
         return KRB5_KDB_DBNOTINITED;
 
-    db_ctx = context->dal_handle->db_context;
-    if ((retval = krb5_db2_lock(context, KRB5_LOCKMODE_EXCLUSIVE)))
+    dbc = context->dal_handle->db_context;
+    if ((retval = ctx_lock(context, dbc, KRB5_LOCKMODE_EXCLUSIVE)))
         return retval;
 
-    db = db_ctx->db;
-    if ((retval = start_update(context))) {
-        (void) krb5_db2_unlock(context);
-        return retval;
-    }
+    db = dbc->db;
 
     retval = krb5_encode_princ_entry(context, &contdata, entry);
     if (retval)
@@ -870,7 +857,7 @@ krb5_db2_put_principal(krb5_context context, krb5_db_entry *entry,
     krb5_free_data_contents(context, &contdata);
 
 cleanup:
-    (void) end_update(context);
+    ctx_update_age(dbc);
     (void) krb5_db2_unlock(context); /* unlock database */
     return (retval);
 }
@@ -880,7 +867,7 @@ krb5_db2_delete_principal(krb5_context context, krb5_const_principal searchfor)
 {
     krb5_error_code retval;
     krb5_db_entry *entry;
-    krb5_db2_context *db_ctx;
+    krb5_db2_context *dbc;
     DB     *db;
     DBT     key, contents;
     krb5_data keydata, contdata;
@@ -889,21 +876,16 @@ krb5_db2_delete_principal(krb5_context context, krb5_const_principal searchfor)
     if (!inited(context))
         return KRB5_KDB_DBNOTINITED;
 
-    db_ctx = context->dal_handle->db_context;
-    if ((retval = krb5_db2_lock(context, KRB5_LOCKMODE_EXCLUSIVE)))
+    dbc = context->dal_handle->db_context;
+    if ((retval = ctx_lock(context, dbc, KRB5_LOCKMODE_EXCLUSIVE)))
         return (retval);
 
-    if ((retval = start_update(context))) {
-        (void) krb5_db2_unlock(context);     /* unlock write lock */
-        return (retval);
-    }
-
     if ((retval = krb5_encode_princ_dbkey(context, &keydata, searchfor)))
         goto cleanup;
     key.data = keydata.data;
     key.size = keydata.length;
 
-    db = db_ctx->db;
+    db = dbc->db;
     dbret = (*db->get) (db, &key, &contents, 0);
     retval = errno;
     switch (dbret) {
@@ -948,55 +930,30 @@ cleankey:
     krb5_free_data_contents(context, &keydata);
 
 cleanup:
-    (void) end_update(context);
+    ctx_update_age(dbc);
     (void) krb5_db2_unlock(context); /* unlock write lock */
     return retval;
 }
 
-krb5_error_code
-krb5_db2_iterate_ext(krb5_context context,
-                     krb5_error_code(*func) (krb5_pointer, krb5_db_entry *),
-                     krb5_pointer func_arg, int backwards, int recursive)
+static krb5_error_code
+ctx_iterate(krb5_context context, krb5_db2_context *dbc,
+            krb5_error_code (*func)(krb5_pointer, krb5_db_entry *),
+            krb5_pointer func_arg)
 {
-    krb5_db2_context *db_ctx;
-    DB     *db;
-    DBT     key, contents;
+    DB *db;
+    DBT key, contents;
     krb5_data contdata;
     krb5_db_entry *entry;
-    krb5_error_code retval;
-    int     dbret;
-    void   *cookie;
-
-    cookie = NULL;
-    if (!inited(context))
-        return KRB5_KDB_DBNOTINITED;
-
-    db_ctx = context->dal_handle->db_context;
-    retval = krb5_db2_lock(context, KRB5_LOCKMODE_SHARED);
+    krb5_error_code retval, retval2;
+    int dbret;
 
+    retval = ctx_lock(context, dbc, KRB5_LOCKMODE_SHARED);
     if (retval)
         return retval;
 
-    db = db_ctx->db;
-    if (recursive && db->type != DB_BTREE) {
-        (void) krb5_db2_unlock(context);
-        return KRB5_KDB_UK_RERROR;      /* Not optimal, but close enough. */
-    }
-
-    if (!recursive) {
-        dbret = (*db->seq) (db, &key, &contents, backwards ? R_LAST : R_FIRST);
-    } else {
-#ifdef HAVE_BT_RSEQ
-        dbret = bt_rseq(db, &key, &contents, &cookie,
-                        backwards ? R_LAST : R_FIRST);
-#else
-        (void) krb5_db2_unlock(context);
-        return KRB5_KDB_UK_RERROR;      /* Not optimal, but close enough. */
-#endif
-    }
+    db = dbc->db;
+    dbret = db->seq(db, &key, &contents, R_FIRST);
     while (dbret == 0) {
-        krb5_error_code retval2;
-
         contdata.data = contents.data;
         contdata.length = contents.size;
         retval = krb5_decode_princ_entry(context, &contdata, &entry);
@@ -1017,18 +974,7 @@ krb5_db2_iterate_ext(krb5_context context,
             retval = retval2;
             break;
         }
-        if (!recursive) {
-            dbret = (*db->seq) (db, &key, &contents,
-                                backwards ? R_PREV : R_NEXT);
-        } else {
-#ifdef HAVE_BT_RSEQ
-            dbret = bt_rseq(db, &key, &contents, &cookie,
-                            backwards ? R_PREV : R_NEXT);
-#else
-            (void) krb5_db2_unlock(context);
-            return KRB5_KDB_UK_RERROR;  /* Not optimal, but close enough. */
-#endif
-        }
+        dbret = db->seq(db, &key, &contents, R_NEXT);
     }
     switch (dbret) {
     case 1:
@@ -1038,7 +984,7 @@ krb5_db2_iterate_ext(krb5_context context,
     default:
         retval = errno;
     }
-    (void) krb5_db2_unlock(context);
+    (void) ctx_unlock(context, dbc);
     return retval;
 }
 
@@ -1047,20 +993,23 @@ krb5_db2_iterate(krb5_context context, char *match_expr,
                  krb5_error_code(*func) (krb5_pointer, krb5_db_entry *),
                  krb5_pointer func_arg)
 {
-    return krb5_db2_iterate_ext(context, func, func_arg, 0, 0);
+    if (!inited(context))
+        return KRB5_KDB_DBNOTINITED;
+    return ctx_iterate(context, context->dal_handle->db_context, func,
+                       func_arg);
 }
 
 krb5_boolean
 krb5_db2_set_lockmode(krb5_context context, krb5_boolean mode)
 {
     krb5_boolean old;
-    krb5_db2_context *db_ctx;
+    krb5_db2_context *dbc;
 
-    db_ctx = context->dal_handle->db_context;
+    dbc = context->dal_handle->db_context;
     old = mode;
-    if (db_ctx) {
-        old = db_ctx->db_nb_locks;
-        db_ctx->db_nb_locks = mode;
+    if (dbc) {
+        old = dbc->db_nb_locks;
+        dbc->db_nb_locks = mode;
     }
     return old;
 }
@@ -1099,14 +1048,14 @@ krb5_db2_open(krb5_context context, char *conf_section, char **db_args,
     if (status != 0)
         return status;
 
-    return init_db2_context(context);
+    return ctx_init(context->dal_handle->db_context);
 }
 
 krb5_error_code
 krb5_db2_create(krb5_context context, char *conf_section, char **db_args)
 {
     krb5_error_code status = 0;
-    krb5_db2_context *db_ctx;
+    krb5_db2_context *dbc;
 
     krb5_clear_error_message(context);
     if (inited(context))
@@ -1116,24 +1065,23 @@ krb5_db2_create(krb5_context context, char *conf_section, char **db_args)
     if (status != 0)
         return status;
 
-    status = check_openable(context);
-    if (status == 0)
-        return EEXIST;
-
-    db_ctx = context->dal_handle->db_context;
-    status = create_db(context, db_ctx->db_name);
+    dbc = context->dal_handle->db_context;
+    status = ctx_create_db(context, dbc);
     if (status != 0)
         return status;
 
-    return init_db2_context(context);
+    if (!dbc->tempdb)
+        krb5_db2_unlock(context);
+
+    return 0;
 }
 
 krb5_error_code
 krb5_db2_destroy(krb5_context context, char *conf_section, char **db_args)
 {
-    krb5_error_code status = 0;
-    krb5_db2_context *db_ctx;
-    char *dbname, policy_db_name[1024], policy_lock_name[1024];
+    krb5_error_code status;
+    krb5_db2_context *dbc;
+    char *dbname = NULL, *lockname = NULL, *polname = NULL, *plockname = NULL;
 
     if (inited(context)) {
         status = krb5_db2_fini(context);
@@ -1150,25 +1098,29 @@ krb5_db2_destroy(krb5_context context, char *conf_section, char **db_args)
     if (status != 0)
         return status;
 
-    db_ctx = context->dal_handle->db_context;
-    dbname = db_ctx->db_name;
+    dbc = context->dal_handle->db_context;
 
-    status = destroy_file_suffix(dbname, "");
+    status = ctx_allfiles(dbc, &dbname, &lockname, &polname, &plockname);
     if (status)
-        return status;
-    status = destroy_file_suffix(dbname, KDB2_LOCK_EXT);
+        goto cleanup;
+    status = destroy_file(dbname);
     if (status)
-        return status;
-
-    snprintf(policy_db_name, sizeof(policy_db_name), "%s.kadm5", dbname);
-    snprintf(policy_lock_name, sizeof(policy_lock_name), "%s.lock",
-             policy_db_name);
-    status = osa_adb_destroy_db(policy_db_name, policy_lock_name,
-                                OSA_ADB_POLICY_DB_MAGIC);
+        goto cleanup;
+    status = unlink(lockname);
+    if (status)
+        goto cleanup;
+    status = osa_adb_destroy_db(polname, plockname, OSA_ADB_POLICY_DB_MAGIC);
     if (status)
         return status;
 
-    return krb5_db2_fini(context);
+    status = krb5_db2_fini(context);
+
+cleanup:
+    free(dbname);
+    free(lockname);
+    free(polname);
+    free(plockname);
+    return status;
 }
 
 void   *
@@ -1234,47 +1186,6 @@ krb5_db2_free_policy(krb5_context context, osa_policy_ent_t entry)
 }
 
 
-/* */
-
-krb5_error_code
-krb5_db2_promote_db(krb5_context context, char *conf_section, char **db_args)
-{
-    krb5_error_code status = 0;
-    char *db_name = NULL;
-    char *temp_db_name = NULL;
-    char **db_argp;
-    int merge_nra = 0;
-    krb5_db2_context *db_ctx = context->dal_handle->db_context;
-
-    krb5_clear_error_message (context);
-
-    db_name = strdup(db_ctx->db_name);
-    if (db_name == NULL) {
-        status = ENOMEM;
-        goto clean_n_exit;
-    }
-
-    status = gen_dbsuffix(db_name, "~", &temp_db_name);
-    if (status)
-        goto clean_n_exit;
-
-    for (db_argp = db_args; *db_argp; db_argp++) {
-        if (!strcmp(*db_argp, "merge_nra")) {
-            merge_nra++;
-            break;
-        }
-    }
-
-    status = krb5_db2_rename(context, temp_db_name, db_name, merge_nra);
-    if (status)
-        goto clean_n_exit;
-
-clean_n_exit:
-    free(db_name);
-    free(temp_db_name);
-    return status;
-}
-
 /*
  * Merge non-replicated attributes from src into dst, setting
  * changed to non-zero if dst was changed.
@@ -1357,192 +1268,141 @@ krb5_db2_merge_nra_iterator(krb5_pointer ptr, krb5_db_entry *entry)
 
 /*
  * Merge non-replicated attributes (that is, lockout-related
- * attributes and negative TL data types) from the old database
- * into the new one.
- *
- * Note: src_db is locked on success.
+ * attributes and negative TL data types) from the real database
+ * into the temporary one.
  */
 static krb5_error_code
-krb5_db2_begin_nra_merge(krb5_context context,
-                         krb5_db2_context *src_db,
-                         krb5_db2_context *dst_db)
+ctx_merge_nra(krb5_context context, krb5_db2_context *dbc_temp,
+              krb5_db2_context *dbc_real)
 {
-    krb5_error_code retval;
-    kdb5_dal_handle *dal_handle = context->dal_handle;
     struct nra_context nra;
 
     nra.kcontext = context;
-    nra.db_context = dst_db;
-
-    assert(dal_handle->db_context == dst_db);
-    dal_handle->db_context = src_db;
-
-    retval = krb5_db2_lock(context, KRB5_LOCKMODE_EXCLUSIVE);
-    if (retval) {
-        dal_handle->db_context = dst_db;
-        return retval;
-    }
-
-    retval = krb5_db2_iterate_ext(context, krb5_db2_merge_nra_iterator,
-                                  &nra, 0, 0);
-    if (retval != 0)
-        (void) krb5_db2_unlock(context);
-
-    dal_handle->db_context = dst_db;
-
-    return retval;
+    nra.db_context = dbc_real;
+    return ctx_iterate(context, dbc_temp, krb5_db2_merge_nra_iterator, &nra);
 }
 
 /*
- * Finish merge of non-replicated attributes by unlocking
- * src_db.
+ * In the filesystem, promote the temporary database described by dbc_temp to
+ * the real database described by dbc_real.  Both must be exclusively locked.
  */
 static krb5_error_code
-krb5_db2_end_nra_merge(krb5_context context,
-                       krb5_db2_context *src_db,
-                       krb5_db2_context *dst_db)
-{
-    krb5_error_code retval;
-    kdb5_dal_handle *dal_handle = context->dal_handle;
-
-    dal_handle->db_context = src_db;
-    retval = krb5_db2_unlock(context);
-    dal_handle->db_context = dst_db;
-
-    return retval;
-}
-
-/* Retrieved from pre-DAL code base.  */
-/*
- * "Atomically" rename the database in a way that locks out read
- * access in the middle of the rename.
- *
- * Not perfect; if we crash in the middle of an update, we don't
- * necessarily know to complete the transaction the rename, but...
- *
- * Since the rename operation happens outside the init/fini bracket, we
- * have to go through the same stuff that we went through up in db_destroy.
- */
-krb5_error_code
-krb5_db2_rename(krb5_context context, char *from, char *to, int merge_nra)
+ctx_promote(krb5_context context, krb5_db2_context *dbc_temp,
+            krb5_db2_context *dbc_real)
 {
-    char *fromok;
     krb5_error_code retval;
-    krb5_db2_context *s_context, *db_ctx;
-    kdb5_dal_handle *dal_handle = context->dal_handle;
+    char *tdb = NULL, *tlock = NULL, *tpol = NULL, *tplock = NULL;
+    char *rdb = NULL, *rlock = NULL, *rpol = NULL, *rplock = NULL;
 
-    s_context = dal_handle->db_context;
-    dal_handle->db_context = NULL;
-    retval = get_context(context, &db_ctx);
+    /* Generate all filenames of interest (including a few we don't need). */
+    retval = ctx_allfiles(dbc_temp, &tdb, &tlock, &tpol, &tplock);
     if (retval)
         return retval;
-
-    /*
-     * Create the database if it does not already exist; the
-     * files must exist because krb5_db2_lock, called below,
-     * will fail otherwise.
-     */
-    retval = create_db(context, to);
-    if (retval != 0 && retval != EEXIST)
-        goto errout;
-
-    /*
-     * Set the database to the target, so that other processes sharing
-     * the target will stop their activity, and notice the new database.
-     */
-    db_ctx->db_name = strdup(to);
-    if (db_ctx->db_name == NULL) {
-        retval = ENOMEM;
-        goto errout;
-    }
-
-    retval = check_openable(context);
+    retval = ctx_allfiles(dbc_real, &rdb, &rlock, &rpol, &rplock);
     if (retval)
-        goto errout;
-
-    retval = init_db2_context(context);
-    if (retval)
-        goto errout;
+        goto cleanup;
 
-    retval = gen_dbsuffix(db_ctx->db_name, KDB2_LOCK_EXT, &db_ctx->db_lf_name);
-    if (retval)
-        goto errout;
-    db_ctx->db_lf_file = open(db_ctx->db_lf_name, O_RDWR|O_CREAT, 0600);
-    if (db_ctx->db_lf_file < 0) {
+    /* Rename the principal and policy databases into place. */
+    if (rename(tdb, rdb)) {
         retval = errno;
-        goto errout;
+        goto cleanup;
+    }
+    if (rename(tpol, rpol)) {
+        retval = errno;
+        goto cleanup;
     }
-    set_cloexec_fd(db_ctx->db_lf_file);
 
-    db_ctx->db_inited = 1;
+    ctx_update_age(dbc_real);
 
-    retval = gen_dbsuffix(from, KDB2_LOCK_EXT, &fromok);
-    if (retval)
-        goto errout;
+    /* Release and remove the temporary DB lockfiles. */
+    (void) unlink(tlock);
+    (void) unlink(tplock);
 
-    if ((retval = krb5_db2_lock(context, KRB5_LOCKMODE_EXCLUSIVE)))
-        goto errfromok;
+cleanup:
+    free(tdb);
+    free(tlock);
+    free(tpol);
+    free(tplock);
+    free(rdb);
+    free(rlock);
+    free(rpol);
+    free(rplock);
+    return retval;
+}
 
-    if ((retval = start_update(context)))
-        goto errfromok;
+krb5_error_code
+krb5_db2_promote_db(krb5_context context, char *conf_section, char **db_args)
+{
+    krb5_error_code retval;
+    krb5_boolean merge_nra = FALSE, real_locked = FALSE;
+    krb5_db2_context *dbc_temp, *dbc_real = NULL;
+    char **db_argp;
 
-    if (merge_nra) {
-        if ((retval = krb5_db2_begin_nra_merge(context, s_context, db_ctx)))
-            goto errfromok;
-    }
+    /* context must be initialized with an exclusively locked temp DB. */
+    if (!inited(context))
+        return KRB5_KDB_DBNOTINITED;
+    dbc_temp = context->dal_handle->db_context;
+    if (dbc_temp->db_lock_mode != KRB5_LOCKMODE_EXCLUSIVE)
+        return KRB5_KDB_NOTLOCKED;
+    if (!dbc_temp->tempdb)
+        return EINVAL;
 
-    if (rename(from, to)) {
-        retval = errno;
-        goto errfromok;
-    }
-    if (unlink(fromok)) {
-        retval = errno;
-        goto errfromok;
+    /* Check db_args for whether we should merge non-replicated attributes. */
+    for (db_argp = db_args; *db_argp; db_argp++) {
+        if (!strcmp(*db_argp, "merge_nra")) {
+            merge_nra = TRUE;
+            break;
+        }
     }
 
+    /* Make a db2 context for the real DB. */
+    dbc_real = k5alloc(sizeof(*dbc_real), &retval);
+    if (dbc_real == NULL)
+        return retval;
+    ctx_clear(dbc_real);
+
+    /* Try creating the real DB. */
+    dbc_real->db_name = strdup(dbc_temp->db_name);
+    if (dbc_real->db_name == NULL)
+        goto cleanup;
+    dbc_real->tempdb = FALSE;
+    retval = ctx_create_db(context, dbc_real);
+    if (retval == EEXIST) {
+        /* The real database already exists, so open and lock it. */
+        dbc_real->db_name = strdup(dbc_temp->db_name);
+        if (dbc_real->db_name == NULL)
+            goto cleanup;
+        dbc_real->tempdb = FALSE;
+        retval = ctx_init(dbc_real);
+        if (retval)
+            goto cleanup;
+        retval = ctx_lock(context, dbc_real, KRB5_DB_LOCKMODE_EXCLUSIVE);
+        if (retval)
+            goto cleanup;
+    } else if (retval)
+        goto cleanup;
+    real_locked = TRUE;
+
     if (merge_nra) {
-        krb5_db2_end_nra_merge(context, s_context, db_ctx);
+        retval = ctx_merge_nra(context, dbc_temp, dbc_real);
+        if (retval)
+            goto cleanup;
     }
 
-    retval = end_update(context);
+    /* Perform filesystem manipulations for the promotion. */
+    retval = ctx_promote(context, dbc_temp, dbc_real);
     if (retval)
-        goto errfromok;
-
-    {
-        /* XXX moved so that NRA merge works */
-        /* Ugly brute force hack.
-
-           Should be going through nice friendly helper routines for
-           this, but it's a mess of jumbled so-called interfaces right
-           now.  */
-        char    policy[2048], new_policy[2048];
-        assert (strlen(db_ctx->db_name) < 2000);
-        snprintf(policy, sizeof(policy), "%s.kadm5", db_ctx->db_name);
-        snprintf(new_policy, sizeof(new_policy),
-                 "%s~.kadm5", db_ctx->db_name);
-        if (0 != rename(new_policy, policy)) {
-            retval = errno;
-            goto errfromok;
-        }
-        strlcat(new_policy, ".lock",sizeof(new_policy));
-        (void) unlink(new_policy);
-    }
-
-errfromok:
-    free(fromok);
-errout:
-    if (dal_handle->db_context) {
-        if (db_ctx->db_lf_file >= 0) {
-            krb5_db2_unlock(context);
-            close(db_ctx->db_lf_file);
-        }
-        clear_context((krb5_db2_context *) dal_handle->db_context);
-        free(dal_handle->db_context);
-    }
+        goto cleanup;
 
-    dal_handle->db_context = s_context;
-    (void) krb5_db2_unlock(context); /* unlock saved context db */
+    /* Unlock and finalize context since the temp DB is gone. */
+    (void) krb5_db2_unlock(context);
+    krb5_db2_fini(context);
 
+cleanup:
+    if (real_locked)
+        (void) ctx_unlock(context, dbc_real);
+    if (dbc_real)
+        ctx_fini(dbc_real);
     return retval;
 }
 
index 0c3b55b958acf431b89b6ef87e32a3c66e2b5e23..efab9225db62c8b1c628f3b8c5bec5c1cd725ab5 100644 (file)
@@ -50,22 +50,14 @@ typedef struct _krb5_db2_context {
 
 #define KRB5_DB2_MAX_RETRY 5
 
-#define KDB2_LOCK_EXT ".ok"
-#define KDB2_TEMP_LOCK_EXT "~.ok"
-
 krb5_error_code krb5_db2_init(krb5_context);
 krb5_error_code krb5_db2_fini(krb5_context);
 krb5_error_code krb5_db2_get_age(krb5_context, char *, time_t *);
-krb5_error_code krb5_db2_rename(krb5_context, char *, char *, int );
 krb5_error_code krb5_db2_get_principal(krb5_context, krb5_const_principal,
                                        unsigned int, krb5_db_entry **);
 void krb5_db2_free_principal(krb5_context, krb5_db_entry *);
 krb5_error_code krb5_db2_put_principal(krb5_context, krb5_db_entry *,
                                        char **db_args);
-krb5_error_code krb5_db2_iterate_ext(krb5_context,
-                                     krb5_error_code (*)(krb5_pointer,
-                                                         krb5_db_entry *),
-                                     krb5_pointer, int, int);
 krb5_error_code krb5_db2_iterate(krb5_context, char *,
                                  krb5_error_code (*)(krb5_pointer,
                                                      krb5_db_entry *),