chiark / gitweb /
hashmap: allow hashmap_move() to fail
[elogind.git] / src / shared / hashmap.c
index 1c3a452899920c9ac5322863aec1c94db8f370bc..6f5f8204dd3dd0d2c733862edaa0b48662af3595 100644 (file)
@@ -369,23 +369,31 @@ static struct hashmap_entry *hash_scan(Hashmap *h, unsigned hash, const void *ke
         return NULL;
 }
 
-static bool resize_buckets(Hashmap *h) {
+static int resize_buckets(Hashmap *h, unsigned entries_add) {
         struct hashmap_entry **n, *i;
-        unsigned m;
+        unsigned m, new_n_entries, new_n_buckets;
         uint8_t nkey[HASH_KEY_SIZE];
 
         assert(h);
 
-        if (_likely_(h->n_entries*4 < h->n_buckets*3))
-                return false;
+        new_n_entries = h->n_entries + entries_add;
+
+        /* overflow? */
+        if (_unlikely_(new_n_entries < entries_add || new_n_entries > UINT_MAX / 4))
+                return -ENOMEM;
+
+        new_n_buckets = new_n_entries * 4 / 3;
+
+        if (_likely_(new_n_buckets <= h->n_buckets))
+                return 0;
 
-        /* Increase by four */
-        m = (h->n_entries+1)*4-1;
+        /* Increase by four at least */
+        m = MAX((h->n_entries+1)*4-1, new_n_buckets);
 
         /* If we hit OOM we simply risk packed hashmaps... */
         n = new0(struct hashmap_entry*, m);
         if (!n)
-                return false;
+                return -ENOMEM;
 
         /* Let's use a different randomized hash key for the
          * extension, so that people cannot guess what we are using
@@ -424,7 +432,7 @@ static bool resize_buckets(Hashmap *h) {
 
         memcpy(h->hash_key, nkey, HASH_KEY_SIZE);
 
-        return true;
+        return 1;
 }
 
 static int __hashmap_put(Hashmap *h, const void *key, void *value, unsigned hash) {
@@ -432,7 +440,7 @@ static int __hashmap_put(Hashmap *h, const void *key, void *value, unsigned hash
 
         struct hashmap_entry *e;
 
-        if (resize_buckets(h))
+        if (resize_buckets(h, 1) > 0)
                 hash = bucket_hash(h, key);
 
         if (h->from_pool)
@@ -795,16 +803,28 @@ int hashmap_merge(Hashmap *h, Hashmap *other) {
         return 0;
 }
 
-void hashmap_move(Hashmap *h, Hashmap *other) {
+int hashmap_reserve(Hashmap *h, unsigned entries_add) {
+        int r;
+
+        assert(h);
+
+        r = resize_buckets(h, entries_add);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int hashmap_move(Hashmap *h, Hashmap *other) {
         struct hashmap_entry *e, *n;
 
         assert(h);
 
         /* The same as hashmap_merge(), but every new item from other
-         * is moved to h. This function is guaranteed to succeed. */
+         * is moved to h. */
 
         if (!other)
-                return;
+                return 0;
 
         for (e = other->iterate_list_head; e; e = n) {
                 unsigned h_hash, other_hash;
@@ -819,6 +839,8 @@ void hashmap_move(Hashmap *h, Hashmap *other) {
                 unlink_entry(other, e, other_hash);
                 link_entry(h, e, h_hash);
         }
+
+        return 0;
 }
 
 int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) {