tdb2: more tests, hash collision fixes, attribute support.
[ccan] / ccan / tdb2 / test / layout.c
1 /* TDB tools to create various canned database layouts. */
2 #include "layout.h"
3 #include <stdlib.h>
4 #include <string.h>
5 #include <assert.h>
6 #include "logging.h"
7
8 struct tdb_layout *new_tdb_layout(void)
9 {
10         struct tdb_layout *layout = malloc(sizeof(*layout));
11         layout->num_elems = 0;
12         layout->elem = NULL;
13         layout->ftable = layout->htable = -1;
14         return layout;
15 }
16
17 static void add(struct tdb_layout *layout, union tdb_layout_elem elem)
18 {
19         layout->elem = realloc(layout->elem,
20                                sizeof(layout->elem[0])
21                                * (layout->num_elems+1));
22         layout->elem[layout->num_elems++] = elem;
23 }
24
25 void tdb_layout_add_free(struct tdb_layout *layout, tdb_len_t len)
26 {
27         union tdb_layout_elem elem;
28         elem.base.type = FREE;
29         elem.free.len = len;
30         add(layout, elem);
31 }
32
33 static struct tdb_data dup_key(struct tdb_data key)
34 {
35         struct tdb_data ret;
36         ret.dsize = key.dsize;
37         ret.dptr = malloc(ret.dsize);
38         memcpy(ret.dptr, key.dptr, ret.dsize);
39         return ret;
40 }
41
42 void tdb_layout_add_used(struct tdb_layout *layout,
43                          TDB_DATA key, TDB_DATA data,
44                          tdb_len_t extra)
45 {
46         union tdb_layout_elem elem;
47         elem.base.type = DATA;
48         elem.used.key = dup_key(key);
49         elem.used.data = dup_key(data);
50         elem.used.extra = extra;
51         add(layout, elem);
52 }
53
54 void tdb_layout_add_hashtable(struct tdb_layout *layout,
55                               unsigned int hash_bits,
56                               tdb_len_t extra)
57 {
58         union tdb_layout_elem elem;
59         elem.base.type = HASHTABLE;
60         elem.hashtable.hash_bits = hash_bits;
61         elem.hashtable.extra = extra;
62         assert(layout->htable == -1U);
63         layout->htable = layout->num_elems;
64         add(layout, elem);
65 }
66
67 void tdb_layout_add_freetable(struct tdb_layout *layout,
68                               unsigned int num_zones,
69                               unsigned int zone_bits,
70                               unsigned int num_buckets,
71                               tdb_len_t extra)
72 {
73         union tdb_layout_elem elem;
74         elem.base.type = FREETABLE;
75         elem.freetable.num_zones = num_zones;
76         elem.freetable.zone_bits = zone_bits;
77         elem.freetable.num_buckets = num_buckets;
78         elem.freetable.extra = extra;
79         assert(layout->ftable == -1U);
80         layout->ftable = layout->num_elems;
81         add(layout, elem);
82 }
83
84 static tdb_len_t free_record_len(tdb_len_t len)
85 {
86         return sizeof(struct tdb_used_record) + len;
87 }
88
89 static tdb_len_t data_record_len(struct tle_used *used)
90 {
91         tdb_len_t len;
92         len = sizeof(struct tdb_used_record)
93                 + used->key.dsize + used->data.dsize + used->extra;
94         assert(len >= sizeof(struct tdb_free_record));
95         return len;
96 }
97
98 static tdb_len_t hashtable_len(struct tle_hashtable *htable)
99 {
100         return sizeof(struct tdb_used_record)
101                 + (sizeof(tdb_off_t) << htable->hash_bits);
102 }
103
104 static tdb_len_t freetable_len(struct tle_freetable *ftable)
105 {
106         return sizeof(struct tdb_used_record)
107                 + (sizeof(tdb_off_t) * ftable->num_zones
108                    * (ftable->num_buckets + 1));
109 }
110
111 static void set_free_record(void *mem, tdb_len_t len)
112 {
113         /* We do all the work in add_to_freetable */
114 }
115
116 static void set_data_record(void *mem, struct tdb_context *tdb,
117                             struct tle_used *used)
118 {
119         struct tdb_used_record *u = mem;
120
121         set_header(tdb, u, used->key.dsize, used->data.dsize,
122                    used->key.dsize + used->data.dsize + used->extra,
123                    tdb_hash(tdb, used->key.dptr, used->key.dsize));
124         memcpy(u + 1, used->key.dptr, used->key.dsize);
125         memcpy((char *)(u + 1) + used->key.dsize,
126                used->data.dptr, used->data.dsize);
127 }
128
129 static void set_hashtable(void *mem, struct tdb_context *tdb,
130                           struct tle_hashtable *htable)
131 {
132         struct tdb_used_record *u = mem;
133         tdb_len_t len = sizeof(tdb_off_t) << htable->hash_bits;
134
135         set_header(tdb, u, 0, len, len + htable->extra, 0);
136         memset(u + 1, 0, len);
137 }
138
139 static void set_freetable(void *mem, struct tdb_context *tdb,
140                           struct tle_freetable *ftable)
141 {
142         struct tdb_used_record *u = mem;
143         tdb_len_t len = sizeof(tdb_off_t) * ftable->num_zones
144                 * (ftable->num_buckets + 1);
145         set_header(tdb, u, 0, len, len + ftable->extra, 0);
146         memset(u + 1, 0, len);
147 }
148
149 static void add_to_freetable(struct tdb_context *tdb,
150                              tdb_off_t eoff,
151                              tdb_off_t elen)
152 {
153         add_free_record(tdb, eoff, sizeof(struct tdb_used_record) + elen);
154 }
155
156 static tdb_off_t hash_off(struct tdb_context *tdb, uint64_t list)
157 {
158         return tdb->header.v.hash_off
159                 + ((list & ((1ULL << tdb->header.v.hash_bits) - 1))
160                    * sizeof(tdb_off_t));
161 }
162
163 static void add_to_hashtable(struct tdb_context *tdb,
164                              tdb_off_t eoff,
165                              struct tdb_data key)
166 {
167         uint64_t hash = tdb_hash(tdb, key.dptr, key.dsize);
168         tdb_off_t hoff;
169
170         while (tdb_read_off(tdb, hoff = hash_off(tdb, hash)) != 0)
171                 hash++;
172
173         tdb_write_off(tdb, hoff, eoff);
174 }
175
176 /* FIXME: Support TDB_CONVERT */
177 struct tdb_context *tdb_layout_get(struct tdb_layout *layout)
178 {
179         unsigned int i;
180         tdb_off_t len;
181         struct tdb_header *hdr;
182         char *mem;
183         struct tdb_context *tdb;
184
185         assert(layout->ftable != -1U);
186         assert(layout->htable != -1U);
187
188         len = sizeof(struct tdb_header);
189
190         /* First pass of layout: calc lengths */
191         for (i = 0; i < layout->num_elems; i++) {
192                 union tdb_layout_elem *e = &layout->elem[i];
193                 e->base.off = len;
194                 switch (e->base.type) {
195                 case FREE:
196                         len += free_record_len(e->free.len);
197                         break;
198                 case DATA:
199                         len += data_record_len(&e->used);
200                         break;
201                 case HASHTABLE:
202                         len += hashtable_len(&e->hashtable);
203                         break;
204                 case FREETABLE:
205                         len += freetable_len(&e->freetable);
206                         break;
207                 }
208         }
209
210         mem = malloc(len);
211         /* Now populate our header, cribbing from a real TDB header. */
212         tdb = tdb_open(NULL, TDB_INTERNAL, O_RDWR, 0, &tap_log_attr);
213         hdr = (void *)mem;
214         *hdr = tdb->header;
215         hdr->v.generation++;
216         hdr->v.num_zones = layout->elem[layout->ftable].freetable.num_zones;
217         hdr->v.zone_bits = layout->elem[layout->ftable].freetable.zone_bits;
218         hdr->v.free_buckets
219                 = layout->elem[layout->ftable].freetable.num_buckets;
220         hdr->v.free_off = layout->elem[layout->ftable].base.off
221                 + sizeof(struct tdb_used_record);
222         hdr->v.hash_bits = layout->elem[layout->htable].hashtable.hash_bits;
223         hdr->v.hash_off = layout->elem[layout->htable].base.off
224                 + sizeof(struct tdb_used_record);
225
226         /* Mug the tdb we have to make it use this. */
227         free(tdb->map_ptr);
228         tdb->map_ptr = mem;
229         tdb->map_size = len;
230         header_changed(tdb);
231
232         for (i = 0; i < layout->num_elems; i++) {
233                 union tdb_layout_elem *e = &layout->elem[i];
234                 switch (e->base.type) {
235                 case FREE:
236                         set_free_record(mem + e->base.off, e->free.len);
237                         break;
238                 case DATA:
239                         set_data_record(mem + e->base.off, tdb, &e->used);
240                         break;
241                 case HASHTABLE:
242                         set_hashtable(mem + e->base.off, tdb, &e->hashtable);
243                         break;
244                 case FREETABLE:
245                         set_freetable(mem + e->base.off, tdb, &e->freetable);
246                         break;
247                 }
248         }
249
250         /* Now fill the free and hash tables. */
251         for (i = 0; i < layout->num_elems; i++) {
252                 union tdb_layout_elem *e = &layout->elem[i];
253                 switch (e->base.type) {
254                 case FREE:
255                         add_to_freetable(tdb, e->base.off, e->free.len);
256                         break;
257                 case DATA:
258                         add_to_hashtable(tdb, e->base.off, e->used.key);
259                         break;
260                 default:
261                         break;
262                 }
263         }
264
265         return tdb;
266 }