tdb: automatically identify Jenkins hash tdbs
[metze/ctdb/wip.git] / lib / tdb / common / open.c
index 3ff6b17c03493980eefe086d57af87badb6a7849..9a82bc9badbbdeccbf0aa6fd85b049c89370c6c4 100644 (file)
 /* all contexts, to ensure no double-opens (fcntl locks don't nest!) */
 static struct tdb_context *tdbs = NULL;
 
-
-/* This is based on the hash algorithm from gdbm */
-static unsigned int default_tdb_hash(TDB_DATA *key)
+/* We use two hashes to double-check they're using the right hash function. */
+void tdb_header_hash(struct tdb_context *tdb,
+                    uint32_t *magic1_hash, uint32_t *magic2_hash)
 {
-       uint32_t value; /* Used to compute the hash value.  */
-       uint32_t   i;   /* Used to cycle through random values. */
+       TDB_DATA hash_key;
+       uint32_t tdb_magic = TDB_MAGIC;
 
-       /* Set the initial value from the key size. */
-       for (value = 0x238F13AF * key->dsize, i=0; i < key->dsize; i++)
-               value = (value + (key->dptr[i] << (i*5 % 24)));
+       hash_key.dptr = discard_const_p(unsigned char, TDB_MAGIC_FOOD);
+       hash_key.dsize = sizeof(TDB_MAGIC_FOOD);
+       *magic1_hash = tdb->hash_fn(&hash_key);
 
-       return (1103515243 * value + 12345);  
-}
+       hash_key.dptr = (unsigned char *)CONVERT(tdb_magic);
+       hash_key.dsize = sizeof(tdb_magic);
+       *magic2_hash = tdb->hash_fn(&hash_key);
 
+       /* Make sure at least one hash is non-zero! */
+       if (*magic1_hash == 0 && *magic2_hash == 0)
+               *magic1_hash = 1;
+}
 
 /* initialise a new database with a specified hash size */
 static int tdb_new_database(struct tdb_context *tdb, int hash_size)
@@ -51,7 +56,6 @@ static int tdb_new_database(struct tdb_context *tdb, int hash_size)
        struct tdb_header *newdb;
        size_t size;
        int ret = -1;
-       ssize_t written;
 
        /* We make it up in memory, then write it out if not internal */
        size = sizeof(struct tdb_header) + (hash_size+1)*sizeof(tdb_off_t);
@@ -63,6 +67,9 @@ static int tdb_new_database(struct tdb_context *tdb, int hash_size)
        /* Fill in the header */
        newdb->version = TDB_VERSION;
        newdb->hash_size = hash_size;
+
+       tdb_header_hash(tdb, &newdb->magic1_hash, &newdb->magic2_hash);
+
        if (tdb->flags & TDB_INTERNAL) {
                tdb->map_size = size;
                tdb->map_ptr = (char *)newdb;
@@ -129,6 +136,23 @@ static void null_log_fn(struct tdb_context *tdb, enum tdb_debug_level level, con
 {
 }
 
+static bool check_header_hash(struct tdb_context *tdb,
+                             bool default_hash, uint32_t *m1, uint32_t *m2)
+{
+       tdb_header_hash(tdb, m1, m2);
+       if (tdb->header.magic1_hash == *m1 &&
+           tdb->header.magic2_hash == *m2) {
+               return true;
+       }
+
+       /* If they explicitly set a hash, always respect it. */
+       if (!default_hash)
+               return false;
+
+       /* Otherwise, try the other inbuilt hash. */
+       tdb->hash_fn = tdb_jenkins_hash;
+       return check_header_hash(tdb, false, m1, m2);
+}
 
 struct tdb_context *tdb_open_ex(const char *name, int hash_size, int tdb_flags,
                                int open_flags, mode_t mode,
@@ -141,6 +165,8 @@ struct tdb_context *tdb_open_ex(const char *name, int hash_size, int tdb_flags,
        unsigned char *vp;
        uint32_t vertest;
        unsigned v;
+       const char *hash_alg;
+       uint32_t magic1, magic2;
 
        if (!(tdb = (struct tdb_context *)calloc(1, sizeof *tdb))) {
                /* Can't log this */
@@ -162,7 +188,14 @@ struct tdb_context *tdb_open_ex(const char *name, int hash_size, int tdb_flags,
                tdb->log.log_fn = null_log_fn;
                tdb->log.log_private = NULL;
        }
-       tdb->hash_fn = hash_fn ? hash_fn : default_tdb_hash;
+
+       if (hash_fn) {
+               tdb->hash_fn = hash_fn;
+               hash_alg = "the user defined";
+       } else {
+               tdb->hash_fn = tdb_old_hash;
+               hash_alg = "either default";
+       }
 
        /* cache the page size */
        tdb->page_size = getpagesize();
@@ -197,6 +230,10 @@ struct tdb_context *tdb_open_ex(const char *name, int hash_size, int tdb_flags,
                goto fail;
        }
 
+       if (getenv("TDB_NO_FSYNC")) {
+               tdb->flags |= TDB_NOSYNC;
+       }
+
        /*
         * TDB_ALLOW_NESTING is the default behavior.
         * Note: this may change in future versions!
@@ -280,6 +317,24 @@ struct tdb_context *tdb_open_ex(const char *name, int hash_size, int tdb_flags,
                goto fail;
        }
 
+       if ((tdb->header.magic1_hash == 0) && (tdb->header.magic2_hash == 0)) {
+               /* older TDB without magic hash references */
+       } else if (!check_header_hash(tdb, !hash_fn, &magic1, &magic2)) {
+               TDB_LOG((tdb, TDB_DEBUG_FATAL, "tdb_open_ex: "
+                        "%s was not created with %s hash function we are using\n"
+                        "magic1_hash[0x%08X %s 0x%08X] "
+                        "magic2_hash[0x%08X %s 0x%08X]\n",
+                        name, hash_alg,
+                        tdb->header.magic1_hash,
+                        (tdb->header.magic1_hash == magic1) ? "==" : "!=",
+                        magic1,
+                        tdb->header.magic2_hash,
+                        (tdb->header.magic2_hash == magic2) ? "==" : "!=",
+                        magic2));
+               errno = EINVAL;
+               goto fail;
+       }
+
        /* Is it already in the open list?  If so, fail. */
        if (tdb_already_open(st.st_dev, st.st_ino)) {
                TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_open_ex: "