Line | Count | Source |
1 | | /* bucket.c - The routines for playing with hash buckets. */ |
2 | | |
3 | | /* This file is part of GDBM, the GNU data base manager. |
4 | | Copyright (C) 1990-2025 Free Software Foundation, Inc. |
5 | | |
6 | | GDBM is free software; you can redistribute it and/or modify |
7 | | it under the terms of the GNU General Public License as published by |
8 | | the Free Software Foundation; either version 3, or (at your option) |
9 | | any later version. |
10 | | |
11 | | GDBM is distributed in the hope that it will be useful, |
12 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | GNU General Public License for more details. |
15 | | |
16 | | You should have received a copy of the GNU General Public License |
17 | | along with GDBM. If not, see <http://www.gnu.org/licenses/>. */ |
18 | | |
19 | | #include "autoconf.h" |
20 | | #include "gdbmdefs.h" |
21 | | #include <stdint.h> |
22 | | #include <limits.h> |
23 | | |
24 | 0 | #define GDBM_MAX_DIR_SIZE INT32_MAX |
25 | 0 | #define GDBM_MAX_DIR_HALF (GDBM_MAX_DIR_SIZE / 2) |
26 | | |
27 | | /* Initializing a new hash buckets sets all bucket entries to -1 hash value. */ |
28 | | void |
29 | | _gdbm_new_bucket (GDBM_FILE dbf, hash_bucket *bucket, int bits) |
30 | 12.0k | { |
31 | 12.0k | int index; |
32 | | |
33 | | /* Initialize the avail block. */ |
34 | 12.0k | bucket->av_count = 0; |
35 | | |
36 | | /* Set the information fields first. */ |
37 | 12.0k | bucket->bucket_bits = bits; |
38 | 12.0k | bucket->count = 0; |
39 | | |
40 | | /* Initialize all bucket elements. */ |
41 | 1.21M | for (index = 0; index < dbf->header->bucket_elems; index++) |
42 | 1.20M | bucket->h_table[index].hash_value = -1; |
43 | 12.0k | } |
44 | | |
45 | | /* Bucket cache table functions */ |
46 | | |
47 | | /* Hash an off_t word into an index of width NBITS. */ |
48 | | static size_t |
49 | | adrhash (off_t adr, size_t nbits) |
50 | 3.88M | { |
51 | 3.88M | adr ^= adr >> (GDBM_HASH_BITS + 1 - nbits); |
52 | 3.88M | return ((265443576910ul * adr) & 0xffffffff) >> (GDBM_HASH_BITS + 1 - nbits); |
53 | 3.88M | } |
54 | | |
55 | | /* |
56 | | * Return a pointer to the cache table slot for bucket address ADR. |
57 | | * Never returns NULL. |
58 | | */ |
59 | | static cache_elem ** |
60 | | cache_tab_lookup_slot (GDBM_FILE dbf, off_t adr) |
61 | 3.70M | { |
62 | 3.70M | cache_elem **cache = dbf->cache; |
63 | 3.70M | size_t h = adrhash (adr, dbf->cache_bits); |
64 | | |
65 | 3.70M | if (cache[h]) |
66 | 3.54M | { |
67 | 3.54M | if (cache[h]->ca_adr != adr) |
68 | 13.7k | { |
69 | 13.7k | cache_elem *prev = cache[h], *p = prev->ca_coll; |
70 | 15.0k | while (p) |
71 | 6.91k | { |
72 | 6.91k | if (p->ca_adr == adr) |
73 | 5.61k | break; |
74 | 1.29k | prev = p; |
75 | 1.29k | p = prev->ca_coll; |
76 | 1.29k | } |
77 | 13.7k | return &prev->ca_coll; |
78 | 13.7k | } |
79 | 3.54M | } |
80 | 3.69M | return &cache[h]; |
81 | 3.70M | } |
82 | | |
83 | | /* LRU list management */ |
84 | | |
85 | | /* |
86 | | * Link ELEM after REF in DBF cache. If REF is NULL, link at head and |
87 | | * set DBF->bucket to point to the ca_bucket of ELEM. |
88 | | */ |
89 | | static void |
90 | | lru_link_elem (GDBM_FILE dbf, cache_elem *elem, cache_elem *ref) |
91 | 3.71M | { |
92 | 3.71M | if (!ref) |
93 | 3.70M | { |
94 | 3.70M | elem->ca_prev = NULL; |
95 | 3.70M | elem->ca_next = dbf->cache_mru; |
96 | 3.70M | if (dbf->cache_mru) |
97 | 3.11M | dbf->cache_mru->ca_prev = elem; |
98 | 594k | else |
99 | 594k | dbf->cache_lru = elem; |
100 | 3.70M | dbf->cache_mru = elem; |
101 | 3.70M | dbf->bucket = dbf->cache_mru->ca_bucket; |
102 | 3.70M | } |
103 | 6.67k | else |
104 | 6.67k | { |
105 | 6.67k | cache_elem *x; |
106 | | |
107 | 6.67k | elem->ca_prev = ref; |
108 | 6.67k | elem->ca_next = ref->ca_next; |
109 | 6.67k | if ((x = ref->ca_next)) |
110 | 5.11k | x->ca_prev = elem; |
111 | 1.56k | else |
112 | 1.56k | dbf->cache_lru = elem; |
113 | 6.67k | ref->ca_next = elem; |
114 | 6.67k | } |
115 | 3.71M | } |
116 | | |
117 | | /* |
118 | | * Unlink ELEM from the list of cache elements in DBF. |
119 | | * If cache_mru gets updated, update DBF->bucket accordingly. |
120 | | */ |
121 | | static void |
122 | | lru_unlink_elem (GDBM_FILE dbf, cache_elem *elem) |
123 | 3.71M | { |
124 | 3.71M | cache_elem *x; |
125 | | |
126 | 3.71M | if ((x = elem->ca_prev)) |
127 | 438k | x->ca_next = elem->ca_next; |
128 | 3.27M | else |
129 | 3.27M | { |
130 | 3.27M | dbf->cache_mru = elem->ca_next; |
131 | 3.27M | dbf->bucket = dbf->cache_mru ? dbf->cache_mru->ca_bucket : NULL; |
132 | 3.27M | } |
133 | 3.71M | if ((x = elem->ca_next)) |
134 | 3.01M | x->ca_prev = elem->ca_prev; |
135 | 700k | else |
136 | 700k | dbf->cache_lru = elem->ca_prev; |
137 | 3.71M | elem->ca_prev = elem->ca_next = NULL; |
138 | 3.71M | } |
139 | | |
140 | | /* Creates and returns new cache element for DBF. The element is initialized, |
141 | | but not linked to the LRU list. |
142 | | Return NULL on error. |
143 | | */ |
144 | | static cache_elem * |
145 | | cache_elem_new (GDBM_FILE dbf, off_t adr) |
146 | 177k | { |
147 | 177k | cache_elem *elem; |
148 | | |
149 | 177k | elem = dbf->cache_avail; |
150 | 177k | if (elem) |
151 | 156k | { |
152 | 156k | dbf->cache_avail = elem->ca_next; |
153 | 156k | } |
154 | 20.5k | else |
155 | 20.5k | { |
156 | 20.5k | elem = calloc (1, |
157 | 20.5k | sizeof (*elem) - |
158 | 20.5k | sizeof (elem->ca_bucket[0]) + |
159 | 20.5k | dbf->header->bucket_size); |
160 | | |
161 | 20.5k | if (!elem) |
162 | 0 | return NULL; |
163 | 20.5k | } |
164 | | |
165 | 177k | elem->ca_adr = adr; |
166 | 177k | elem->ca_changed = FALSE; |
167 | 177k | elem->ca_data.hash_val = -1; |
168 | 177k | elem->ca_data.elem_loc = -1; |
169 | | |
170 | 177k | elem->ca_prev = elem->ca_next = elem->ca_coll = NULL; |
171 | 177k | elem->ca_hits = 0; |
172 | | |
173 | 177k | return elem; |
174 | 177k | } |
175 | | |
176 | | /* Frees element ELEM. Unlinks it from the cache tree and LRU list. */ |
177 | | static void |
178 | | cache_elem_free (GDBM_FILE dbf, cache_elem *elem) |
179 | 177k | { |
180 | 177k | size_t h = adrhash (elem->ca_adr, dbf->cache_bits); |
181 | 177k | cache_elem **pp; |
182 | | |
183 | 177k | lru_unlink_elem (dbf, elem); |
184 | | |
185 | 177k | elem->ca_next = dbf->cache_avail; |
186 | 177k | dbf->cache_avail = elem; |
187 | 177k | dbf->cache_num--; |
188 | | |
189 | 177k | pp = &dbf->cache[h]; |
190 | 186k | while (*pp) |
191 | 186k | { |
192 | 186k | if (*pp == elem) |
193 | 177k | { |
194 | 177k | *pp = (*pp)->ca_coll; |
195 | 177k | break; |
196 | 177k | } |
197 | 9.15k | pp = &(*pp)->ca_coll; |
198 | 9.15k | } |
199 | 177k | } |
200 | | |
201 | | /* Free the least recently used cache entry. */ |
202 | | static inline int |
203 | | cache_lru_free (GDBM_FILE dbf) |
204 | 0 | { |
205 | 0 | cache_elem *last = dbf->cache_lru; |
206 | 0 | if (last->ca_changed) |
207 | 0 | { |
208 | 0 | if (_gdbm_write_bucket (dbf, last)) |
209 | 0 | return -1; |
210 | 0 | } |
211 | 0 | cache_elem_free (dbf, last); |
212 | 0 | return 0; |
213 | 0 | } |
214 | | |
215 | | /* |
216 | | * Round up V to the next highest power of 2 and compute log2 of |
217 | | * it using De Brujin sequences. |
218 | | * See http://supertech.csail.mit.edu/papers/debruijn.pdf |
219 | | */ |
220 | | static unsigned |
221 | | log2i (unsigned v) |
222 | 0 | { |
223 | 0 | static const int dbp[32] = |
224 | 0 | { |
225 | 0 | 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, |
226 | 0 | 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 |
227 | 0 | }; |
228 | |
|
229 | 0 | v--; |
230 | 0 | v |= v >> 1; |
231 | 0 | v |= v >> 2; |
232 | 0 | v |= v >> 4; |
233 | 0 | v |= v >> 8; |
234 | 0 | v |= v >> 16; |
235 | 0 | v++; |
236 | 0 | return dbp[(uint32_t)(v * 0x077CB531U) >> 27]; |
237 | 0 | } |
238 | | |
239 | | static int |
240 | | cache_tab_resize (GDBM_FILE dbf, int bits) |
241 | 8.13k | { |
242 | 8.13k | size_t size = 1 << bits; |
243 | | |
244 | 8.13k | if (!dbf->cache || size != dbf->cache_size) |
245 | 8.13k | { |
246 | 8.13k | size_t n = size * sizeof (dbf->cache[0]); |
247 | 8.13k | cache_elem **p, *elem; |
248 | | |
249 | | /* Flush existing cache */ |
250 | 8.13k | if (_gdbm_cache_flush (dbf)) |
251 | 0 | return -1; |
252 | | |
253 | | /* Reallocate it */ |
254 | 8.13k | p = realloc (dbf->cache, n); |
255 | 8.13k | if (!p) |
256 | 0 | { |
257 | 0 | GDBM_SET_ERRNO (dbf, GDBM_MALLOC_ERROR, FALSE); |
258 | 0 | return -1; |
259 | 0 | } |
260 | 8.13k | dbf->cache = p; |
261 | 8.13k | dbf->cache_size = size; |
262 | 8.13k | dbf->cache_bits = bits; |
263 | | |
264 | 8.13k | memset (dbf->cache, 0, n); |
265 | | |
266 | | /* Rehash and free surplus elements */ |
267 | 8.13k | for (elem = dbf->cache_lru; elem; ) |
268 | 0 | { |
269 | 0 | cache_elem *prev = elem->ca_prev; |
270 | 0 | elem->ca_coll = NULL; |
271 | 0 | if (size < dbf->cache_num) |
272 | 0 | { |
273 | 0 | cache_elem_free (dbf, elem); |
274 | 0 | } |
275 | 0 | else |
276 | 0 | { |
277 | 0 | p = cache_tab_lookup_slot (dbf, elem->ca_adr); |
278 | 0 | if (*p) |
279 | 0 | abort ();// shouldn't happen |
280 | 0 | *p = elem; |
281 | 0 | } |
282 | 0 | elem = prev; |
283 | 0 | } |
284 | 8.13k | } |
285 | 8.13k | return 0; |
286 | 8.13k | } |
287 | | |
288 | | enum |
289 | | { |
290 | | cache_found, |
291 | | cache_new, |
292 | | cache_failure |
293 | | }; |
294 | | |
295 | | static int |
296 | | cache_lookup (GDBM_FILE dbf, off_t adr, cache_elem *ref, cache_elem **ret_elem) |
297 | 3.70M | { |
298 | 3.70M | int rc; |
299 | 3.70M | cache_elem **elp, *elem; |
300 | | |
301 | 3.70M | dbf->cache_access_count++; |
302 | | |
303 | 3.70M | elp = cache_tab_lookup_slot (dbf, adr); |
304 | | |
305 | 3.70M | if (*elp != NULL) |
306 | 3.53M | { |
307 | 3.53M | elem = *elp; |
308 | 3.53M | elem->ca_hits++; |
309 | 3.53M | dbf->cache_hits++; |
310 | 3.53M | lru_unlink_elem (dbf, elem); |
311 | 3.53M | rc = cache_found; |
312 | 3.53M | } |
313 | 177k | else if ((elem = cache_elem_new (dbf, adr)) == NULL) |
314 | 0 | return cache_failure; |
315 | 177k | else |
316 | 177k | { |
317 | 177k | rc = cache_new; |
318 | | |
319 | 177k | if (dbf->cache_num == dbf->cache_size) |
320 | 0 | { |
321 | 0 | if (dbf->cache_auto && dbf->cache_bits < dbf->header->dir_bits && |
322 | 0 | cache_tab_resize (dbf, dbf->cache_bits + 1) == 0) |
323 | 0 | { |
324 | | /* Table has been reallocated, recompute the slot. */ |
325 | 0 | elp = cache_tab_lookup_slot (dbf, adr); |
326 | 0 | } |
327 | 0 | else if (cache_lru_free (dbf)) |
328 | 0 | { |
329 | 0 | rc = cache_failure; |
330 | 0 | } |
331 | 0 | } |
332 | | |
333 | 177k | if (rc == cache_new) |
334 | 177k | { |
335 | 177k | *elp = elem; |
336 | 177k | dbf->cache_num++; |
337 | 177k | } |
338 | 177k | } |
339 | | |
340 | | /* |
341 | | * If the obtained bucket is not changed and is going to become current, |
342 | | * flush all changed cache elements. This ensures that changed cache |
343 | | * elements form a contiguous sequence at the head of the cache list (see |
344 | | * _gdbm_cache_flush). |
345 | | */ |
346 | 3.70M | if (ref == NULL && !elem->ca_changed) |
347 | 3.70M | _gdbm_cache_flush (dbf); |
348 | | |
349 | 3.70M | lru_link_elem (dbf, elem, ref); |
350 | 3.70M | if (rc != cache_failure) |
351 | 3.70M | *ret_elem = elem; |
352 | 3.70M | return rc; |
353 | 3.70M | } |
354 | | |
355 | | /* |
356 | | * Find a bucket for DBF that is pointed to by the bucket directory from |
357 | | * location DIR_INDEX. The bucket cache is first checked to see if it |
358 | | * is already in memory. If not, the last recently used bucket may be |
359 | | * tossed (if the cache is full) to read the new bucket. |
360 | | * |
361 | | * On success, the cached entry with the requested bucket is placed at |
362 | | * the head of the cache list (cache_mru) and the requested bucket becomes |
363 | | * "current". |
364 | | * |
365 | | * On error, the current bucket remains unchanged. |
366 | | */ |
367 | | int |
368 | | _gdbm_get_bucket (GDBM_FILE dbf, int dir_index) |
369 | 3.76M | { |
370 | 3.76M | int rc; |
371 | 3.76M | off_t bucket_adr; /* The address of the correct hash bucket. */ |
372 | 3.76M | off_t file_pos; /* The return address for lseek. */ |
373 | 3.76M | hash_bucket *bucket; |
374 | 3.76M | cache_elem *elem; |
375 | | |
376 | 3.76M | if (!gdbm_dir_entry_valid_p (dbf, dir_index)) |
377 | 60.9k | { |
378 | | /* FIXME: negative caching? */ |
379 | 60.9k | GDBM_SET_ERRNO (dbf, GDBM_BAD_DIR_ENTRY, TRUE); |
380 | 60.9k | return -1; |
381 | 60.9k | } |
382 | | |
383 | | /* Initial set up. */ |
384 | 3.70M | dbf->bucket_dir = dir_index; |
385 | 3.70M | bucket_adr = dbf->dir[dir_index]; |
386 | | |
387 | 3.70M | switch (cache_lookup (dbf, bucket_adr, NULL, &elem)) |
388 | 3.70M | { |
389 | 3.53M | case cache_found: |
390 | 3.53M | break; |
391 | | |
392 | 170k | case cache_new: |
393 | | /* Position the file pointer */ |
394 | 170k | file_pos = gdbm_file_seek (dbf, bucket_adr, SEEK_SET); |
395 | 170k | if (file_pos != bucket_adr) |
396 | 0 | { |
397 | 0 | GDBM_SET_ERRNO (dbf, GDBM_FILE_SEEK_ERROR, TRUE); |
398 | 0 | cache_elem_free (dbf, elem); |
399 | 0 | _gdbm_fatal (dbf, _("lseek error")); |
400 | 0 | return -1; |
401 | 0 | } |
402 | | |
403 | | /* Read the bucket. */ |
404 | 170k | rc = _gdbm_full_read (dbf, elem->ca_bucket, dbf->header->bucket_size); |
405 | 170k | if (rc) |
406 | 143k | { |
407 | 143k | GDBM_DEBUG (GDBM_DEBUG_ERR, |
408 | 143k | "%s: error reading bucket: %s", |
409 | 143k | dbf->name, gdbm_db_strerror (dbf)); |
410 | 143k | dbf->need_recovery = TRUE; |
411 | 143k | cache_elem_free (dbf, elem); |
412 | 143k | _gdbm_fatal (dbf, gdbm_db_strerror (dbf)); |
413 | 143k | return -1; |
414 | 143k | } |
415 | | |
416 | | /* Validate the bucket */ |
417 | 26.6k | bucket = elem->ca_bucket; |
418 | 26.6k | if (!(bucket->count >= 0 |
419 | 25.7k | && bucket->count <= dbf->header->bucket_elems |
420 | 22.1k | && bucket->bucket_bits >= 0 |
421 | 21.6k | && bucket->bucket_bits <= dbf->header->dir_bits)) |
422 | 8.14k | { |
423 | 8.14k | GDBM_SET_ERRNO (dbf, GDBM_BAD_BUCKET, TRUE); |
424 | 8.14k | cache_elem_free (dbf, elem); |
425 | 8.14k | return -1; |
426 | 8.14k | } |
427 | | /* Validate bucket_avail table */ |
428 | 18.5k | if (gdbm_bucket_avail_table_validate (dbf, bucket)) |
429 | 4.49k | { |
430 | 4.49k | cache_elem_free (dbf, elem); |
431 | 4.49k | return -1; |
432 | 4.49k | } |
433 | | |
434 | | /* Update the cache */ |
435 | 14.0k | elem->ca_adr = bucket_adr; |
436 | 14.0k | elem->ca_data.elem_loc = -1; |
437 | 14.0k | elem->ca_changed = FALSE; |
438 | | |
439 | 14.0k | break; |
440 | | |
441 | 0 | case cache_failure: |
442 | 0 | return -1; |
443 | 3.70M | } |
444 | | |
445 | 3.54M | return 0; |
446 | 3.70M | } |
447 | | |
448 | | /* Split the current bucket. This includes moving all items in the bucket to |
449 | | a new bucket. This doesn't require any disk reads because all hash values |
450 | | are stored in the buckets. Splitting the current bucket may require |
451 | | doubling the size of the hash directory. */ |
452 | | int |
453 | | _gdbm_split_bucket (GDBM_FILE dbf, int next_insert) |
454 | 2.69k | { |
455 | 2.69k | off_t old_adr[GDBM_HASH_BITS]; /* Address of the old directories. */ |
456 | 2.69k | int old_size[GDBM_HASH_BITS]; /* Size of the old directories. */ |
457 | 2.69k | int old_count; /* Number of old directories. */ |
458 | | |
459 | 2.69k | int index; /* Used in array indexing. */ |
460 | 2.69k | int index1; /* Used in array indexing. */ |
461 | | |
462 | | /* No directories are yet old. */ |
463 | 2.69k | old_count = 0; |
464 | 6.03k | while (dbf->bucket->count == dbf->header->bucket_elems) |
465 | 3.33k | { |
466 | 3.33k | int new_bits; /* The number of bits for the new buckets. */ |
467 | 3.33k | cache_elem *newcache[2]; /* Location in the cache for the buckets. */ |
468 | 3.33k | off_t adr_0; /* File address of the new bucket 0. */ |
469 | 3.33k | off_t adr_1; /* File address of the new bucket 1. */ |
470 | 3.33k | avail_elem old_bucket; /* Avail Struct for the old bucket. */ |
471 | | |
472 | 3.33k | off_t dir_start0; /* Used in updating the directory. */ |
473 | 3.33k | off_t dir_start1; |
474 | 3.33k | off_t dir_end; |
475 | | |
476 | 3.33k | new_bits = dbf->bucket->bucket_bits + 1; |
477 | | |
478 | | /* |
479 | | * Allocate two new buckets. They will be populated with the entries |
480 | | * from the current bucket (cache_mru->bucket), so make sure that |
481 | | * cache_mru remains unchanged until both buckets are fully formed. |
482 | | * Newly allocated buckets must be linked right after cache_mru, so |
483 | | * that all changed buckets form a contiguous sequence at the beginning |
484 | | * of the cache list (this is needed by _gdbm_cache_flush). |
485 | | */ |
486 | 3.33k | adr_0 = _gdbm_alloc (dbf, dbf->header->bucket_size); |
487 | 3.33k | switch (cache_lookup (dbf, adr_0, dbf->cache_mru, &newcache[0])) |
488 | 3.33k | { |
489 | 3.33k | case cache_new: |
490 | 3.33k | break; |
491 | | |
492 | 0 | case cache_found: |
493 | | /* should not happen */ |
494 | 0 | GDBM_DEBUG (GDBM_DEBUG_ERR, |
495 | 0 | "%s: bucket found where it should not", |
496 | 0 | dbf->name); |
497 | 0 | GDBM_SET_ERRNO (dbf, GDBM_BUCKET_CACHE_CORRUPTED, TRUE); |
498 | 0 | return -1; |
499 | | |
500 | 0 | case cache_failure: |
501 | 0 | return -1; |
502 | 3.33k | } |
503 | 3.33k | _gdbm_new_bucket (dbf, newcache[0]->ca_bucket, new_bits); |
504 | | |
505 | 3.33k | adr_1 = _gdbm_alloc (dbf, dbf->header->bucket_size); |
506 | 3.33k | switch (cache_lookup (dbf, adr_1, newcache[0], &newcache[1])) |
507 | 3.33k | { |
508 | 3.33k | case cache_new: |
509 | 3.33k | break; |
510 | | |
511 | 0 | case cache_found: |
512 | | /* should not happen */ |
513 | 0 | GDBM_DEBUG (GDBM_DEBUG_ERR, |
514 | 0 | "%s: bucket found where it should not", |
515 | 0 | dbf->name); |
516 | 0 | GDBM_SET_ERRNO (dbf, GDBM_BUCKET_CACHE_CORRUPTED, TRUE); |
517 | 0 | return -1; |
518 | | |
519 | 0 | case cache_failure: |
520 | 0 | return -1; |
521 | 3.33k | } |
522 | 3.33k | _gdbm_new_bucket (dbf, newcache[1]->ca_bucket, new_bits); |
523 | | |
524 | | /* Double the directory size if necessary. */ |
525 | 3.33k | if (dbf->header->dir_bits == dbf->bucket->bucket_bits) |
526 | 0 | { |
527 | 0 | off_t *new_dir; /* Pointer to the new directory. */ |
528 | 0 | int dir_size; /* Size of the new directory. */ |
529 | 0 | off_t dir_adr; /* Address of the new directory. */ |
530 | | |
531 | 0 | if (dbf->header->dir_size >= GDBM_MAX_DIR_HALF) |
532 | 0 | { |
533 | 0 | GDBM_SET_ERRNO (dbf, GDBM_DIR_OVERFLOW, TRUE); |
534 | 0 | _gdbm_fatal (dbf, _("directory overflow")); |
535 | 0 | return -1; |
536 | 0 | } |
537 | 0 | dir_size = dbf->header->dir_size * 2; |
538 | 0 | dir_adr = _gdbm_alloc (dbf, dir_size); |
539 | 0 | if (dir_adr == 0) |
540 | 0 | return -1; |
541 | 0 | new_dir = malloc (dir_size); |
542 | 0 | if (new_dir == NULL) |
543 | 0 | { |
544 | 0 | GDBM_SET_ERRNO (dbf, GDBM_MALLOC_ERROR, TRUE); |
545 | 0 | _gdbm_fatal (dbf, _("malloc error")); |
546 | 0 | return -1; |
547 | 0 | } |
548 | | |
549 | 0 | for (index = 0; index < GDBM_DIR_COUNT (dbf); index++) |
550 | 0 | { |
551 | 0 | new_dir[2*index] = dbf->dir[index]; |
552 | 0 | new_dir[2*index+1] = dbf->dir[index]; |
553 | 0 | } |
554 | | |
555 | | /* Update header. */ |
556 | 0 | old_adr[old_count] = dbf->header->dir; |
557 | 0 | dbf->header->dir = dir_adr; |
558 | 0 | old_size[old_count] = dbf->header->dir_size; |
559 | 0 | dbf->header->dir_size = dir_size; |
560 | 0 | dbf->header->dir_bits = new_bits; |
561 | 0 | old_count++; |
562 | | |
563 | | /* Now update dbf. */ |
564 | 0 | dbf->header_changed = TRUE; |
565 | 0 | dbf->bucket_dir *= 2; |
566 | 0 | free (dbf->dir); |
567 | 0 | dbf->dir = new_dir; |
568 | 0 | } |
569 | | |
570 | | /* Copy all elements in dbf->bucket into the new buckets. */ |
571 | 248k | for (index = 0; index < dbf->header->bucket_elems; index++) |
572 | 244k | { |
573 | 244k | bucket_element *old_el = &dbf->bucket->h_table[index]; |
574 | 244k | hash_bucket *bucket; |
575 | 244k | int elem_loc; |
576 | | |
577 | 244k | if (old_el->hash_value < 0) |
578 | 0 | { |
579 | 0 | GDBM_SET_ERRNO (dbf, GDBM_BAD_BUCKET, TRUE); |
580 | 0 | return -1; |
581 | 0 | } |
582 | | |
583 | 244k | bucket = |
584 | 244k | newcache[(old_el->hash_value >> (GDBM_HASH_BITS - new_bits)) & 1]->ca_bucket; |
585 | 244k | elem_loc = old_el->hash_value % dbf->header->bucket_elems; |
586 | 963k | while (bucket->h_table[elem_loc].hash_value != -1) |
587 | 718k | elem_loc = (elem_loc + 1) % dbf->header->bucket_elems; |
588 | 244k | bucket->h_table[elem_loc] = *old_el; |
589 | 244k | bucket->count++; |
590 | 244k | } |
591 | | |
592 | | /* Allocate avail space for the newcache[1]->ca_bucket. */ |
593 | 3.33k | newcache[1]->ca_bucket->bucket_avail[0].av_adr |
594 | 3.33k | = _gdbm_alloc (dbf, dbf->header->block_size); |
595 | 3.33k | if (newcache[1]->ca_bucket->bucket_avail[0].av_adr == 0) |
596 | 0 | return -1; |
597 | 3.33k | newcache[1]->ca_bucket->bucket_avail[0].av_size |
598 | 3.33k | = dbf->header->block_size; |
599 | 3.33k | newcache[1]->ca_bucket->av_count = 1; |
600 | | |
601 | | /* Copy the avail elements in dbf->bucket to newcache[0]->ca_bucket. */ |
602 | 3.33k | newcache[0]->ca_bucket->av_count = dbf->bucket->av_count; |
603 | 3.33k | index = 0; |
604 | 3.33k | if (newcache[0]->ca_bucket->av_count == BUCKET_AVAIL) |
605 | 28 | { |
606 | | /* The avail is full, move the first one to newcache[1]->ca_bucket.*/ |
607 | 28 | _gdbm_put_av_elem (dbf->bucket->bucket_avail[0], |
608 | 28 | newcache[1]->ca_bucket->bucket_avail, |
609 | 28 | &newcache[1]->ca_bucket->av_count, |
610 | 28 | dbf->coalesce_blocks); |
611 | 28 | index = 1; |
612 | 28 | newcache[0]->ca_bucket->av_count--; |
613 | 28 | } |
614 | | |
615 | 3.33k | index1 = 0; |
616 | 14.6k | for (; index < dbf->bucket->av_count; index++) |
617 | 11.3k | { |
618 | 11.3k | newcache[0]->ca_bucket->bucket_avail[index1++] |
619 | 11.3k | = dbf->bucket->bucket_avail[index]; |
620 | 11.3k | } |
621 | | |
622 | | /* Update the directory. We have new file addresses for both buckets. */ |
623 | 3.33k | dir_start1 = (dbf->bucket_dir >> (dbf->header->dir_bits - new_bits)) | 1; |
624 | 3.33k | dir_end = (dir_start1 + 1) << (dbf->header->dir_bits - new_bits); |
625 | 3.33k | dir_start1 = dir_start1 << (dbf->header->dir_bits - new_bits); |
626 | 3.33k | dir_start0 = dir_start1 - (dir_end - dir_start1); |
627 | 193k | for (index = dir_start0; index < dir_start1; index++) |
628 | 190k | dbf->dir[index] = adr_0; |
629 | 193k | for (index = dir_start1; index < dir_end; index++) |
630 | 190k | dbf->dir[index] = adr_1; |
631 | | |
632 | | /* Set changed flags. */ |
633 | 3.33k | newcache[0]->ca_changed = TRUE; |
634 | 3.33k | newcache[1]->ca_changed = TRUE; |
635 | 3.33k | dbf->directory_changed = TRUE; |
636 | | |
637 | | /* Update the cache! */ |
638 | 3.33k | dbf->bucket_dir = _gdbm_bucket_dir (dbf, next_insert); |
639 | | |
640 | | /* Invalidate old cache entry. */ |
641 | 3.33k | avail_elem_init (&old_bucket, |
642 | 3.33k | dbf->header->bucket_size, |
643 | 3.33k | dbf->cache_mru->ca_adr); |
644 | 3.33k | cache_elem_free (dbf, dbf->cache_mru); |
645 | | |
646 | | /* Set dbf->bucket to the proper bucket. */ |
647 | 3.33k | if (dbf->dir[dbf->bucket_dir] != adr_0) |
648 | 1.86k | { |
649 | 1.86k | cache_elem *t = newcache[0]; |
650 | 1.86k | newcache[0] = newcache[1]; |
651 | 1.86k | newcache[1] = t; |
652 | 1.86k | } |
653 | | |
654 | 3.33k | _gdbm_put_av_elem (old_bucket, |
655 | 3.33k | newcache[1]->ca_bucket->bucket_avail, |
656 | 3.33k | &newcache[1]->ca_bucket->av_count, |
657 | 3.33k | dbf->coalesce_blocks); |
658 | | |
659 | 3.33k | lru_unlink_elem (dbf, newcache[0]); |
660 | 3.33k | lru_link_elem (dbf, newcache[0], NULL); |
661 | 3.33k | } |
662 | | |
663 | | /* Get rid of old directories. */ |
664 | 2.69k | for (index = 0; index < old_count; index++) |
665 | 0 | if (_gdbm_free (dbf, old_adr[index], old_size[index])) |
666 | 0 | return -1; |
667 | | |
668 | 2.69k | return 0; |
669 | 2.69k | } |
670 | | |
671 | | |
672 | | /* The only place where a bucket is written. CA_ENTRY is the |
673 | | cache entry containing the bucket to be written. */ |
674 | | |
675 | | int |
676 | | _gdbm_write_bucket (GDBM_FILE dbf, cache_elem *ca_entry) |
677 | 228k | { |
678 | 228k | int rc; |
679 | 228k | off_t file_pos; /* The return value for lseek. */ |
680 | | |
681 | 228k | file_pos = gdbm_file_seek (dbf, ca_entry->ca_adr, SEEK_SET); |
682 | 228k | if (file_pos != ca_entry->ca_adr) |
683 | 0 | { |
684 | 0 | GDBM_SET_ERRNO (dbf, GDBM_FILE_SEEK_ERROR, TRUE); |
685 | 0 | _gdbm_fatal (dbf, _("lseek error")); |
686 | 0 | return -1; |
687 | 0 | } |
688 | 228k | rc = _gdbm_full_write (dbf, ca_entry->ca_bucket, dbf->header->bucket_size); |
689 | 228k | if (rc) |
690 | 0 | { |
691 | 0 | GDBM_DEBUG (GDBM_DEBUG_STORE|GDBM_DEBUG_ERR, |
692 | 0 | "%s: error writing bucket: %s", |
693 | 0 | dbf->name, gdbm_db_strerror (dbf)); |
694 | 0 | _gdbm_fatal (dbf, gdbm_strerror (rc)); |
695 | 0 | return -1; |
696 | 0 | } |
697 | | |
698 | 228k | ca_entry->ca_changed = FALSE; |
699 | 228k | ca_entry->ca_data.hash_val = -1; |
700 | 228k | ca_entry->ca_data.elem_loc = -1; |
701 | 228k | return 0; |
702 | 228k | } |
703 | | |
704 | | /* Cache manipulation interface functions. */ |
705 | | |
706 | 16.2k | #define INIT_CACHE_BITS 9 |
707 | | |
708 | | /* Initialize the bucket cache. */ |
709 | | int |
710 | | _gdbm_cache_init (GDBM_FILE dbf, size_t size) |
711 | 8.13k | { |
712 | 8.13k | int bits; |
713 | 8.13k | int cache_auto; |
714 | | |
715 | 8.13k | if (size == GDBM_CACHE_AUTO) |
716 | 8.13k | { |
717 | 8.13k | cache_auto = TRUE; |
718 | 8.13k | bits = dbf->cache ? dbf->cache_bits : INIT_CACHE_BITS; |
719 | 8.13k | } |
720 | 0 | else if (size > SIZE_T_MAX / sizeof (dbf->cache[0])) |
721 | 0 | { |
722 | 0 | GDBM_SET_ERRNO (dbf, GDBM_OPT_BADVAL, FALSE); |
723 | 0 | return -1; |
724 | 0 | } |
725 | 0 | else |
726 | 0 | { |
727 | 0 | cache_auto = FALSE; |
728 | 0 | bits = log2i (size < 4 ? 4 : size); |
729 | 0 | } |
730 | | |
731 | 8.13k | dbf->cache_auto = cache_auto; |
732 | | |
733 | 8.13k | return cache_tab_resize (dbf, bits); |
734 | 8.13k | } |
735 | | |
736 | | /* Free the bucket cache */ |
737 | | void |
738 | | _gdbm_cache_free (GDBM_FILE dbf) |
739 | 9.09k | { |
740 | 9.09k | cache_elem *elem; |
741 | | |
742 | 26.4k | while (dbf->cache_lru) |
743 | 17.3k | cache_elem_free (dbf, dbf->cache_lru); |
744 | 9.09k | free (dbf->cache); |
745 | 9.09k | dbf->cache = NULL; |
746 | 29.6k | while ((elem = dbf->cache_avail) != NULL) |
747 | 20.5k | { |
748 | 20.5k | dbf->cache_avail = elem->ca_next; |
749 | 20.5k | free (elem->ca_data.dptr); |
750 | 20.5k | free (elem); |
751 | 20.5k | } |
752 | 9.09k | } |
753 | | |
754 | | /* |
755 | | * Flush cache content to disk. |
756 | | * All cache elements with the changed buckets form a contiguous sequence |
757 | | * at the head of the cache list (starting with cache_mru). |
758 | | */ |
759 | | int |
760 | | _gdbm_cache_flush (GDBM_FILE dbf) |
761 | 3.95M | { |
762 | 3.95M | cache_elem *elem; |
763 | 4.18M | for (elem = dbf->cache_mru; elem && elem->ca_changed; elem = elem->ca_next) |
764 | 228k | { |
765 | 228k | if (_gdbm_write_bucket (dbf, elem)) |
766 | 0 | return -1; |
767 | 228k | } |
768 | 3.95M | return 0; |
769 | 3.95M | } |
770 | | |
771 | | |
772 | | void |
773 | | gdbm_get_cache_stats (GDBM_FILE dbf, |
774 | | size_t *access_count, |
775 | | size_t *cache_hits, |
776 | | size_t *cache_count, |
777 | | struct gdbm_cache_stat *bstat, |
778 | | size_t nstat) |
779 | 0 | { |
780 | 0 | if (access_count) |
781 | 0 | *access_count = dbf->cache_access_count; |
782 | 0 | if (cache_hits) |
783 | 0 | *cache_hits = dbf->cache_hits; |
784 | 0 | if (cache_count) |
785 | 0 | *cache_count = dbf->cache_num; |
786 | 0 | if (bstat) |
787 | 0 | { |
788 | 0 | size_t i; |
789 | 0 | cache_elem *elem; |
790 | |
|
791 | 0 | if (nstat > dbf->cache_num) |
792 | 0 | nstat = dbf->cache_num; |
793 | | |
794 | 0 | for (i = 0, elem = dbf->cache_mru; i < nstat; i++, elem = elem->ca_next) |
795 | 0 | { |
796 | 0 | bstat[i].adr = elem->ca_adr; |
797 | 0 | bstat[i].hits = elem->ca_hits; |
798 | 0 | } |
799 | 0 | } |
800 | 0 | } |