Line | Count | Source |
1 | | /* falloc.c - The file space management routines for dbm. */ |
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 system configuration before all else. */ |
20 | | #include "autoconf.h" |
21 | | |
22 | | #include "gdbmdefs.h" |
23 | | |
24 | | |
25 | | /* The forward definitions for this file. See the functions for |
26 | | the definition of the function. */ |
27 | | |
28 | | static avail_elem get_elem (int, avail_elem [], int *); |
29 | | static avail_elem get_block (int, GDBM_FILE); |
30 | | static int push_avail_block (GDBM_FILE); |
31 | | static int pop_avail_block (GDBM_FILE); |
32 | | static int adjust_bucket_avail (GDBM_FILE); |
33 | | |
34 | | int |
35 | | _gdbm_avail_block_read (GDBM_FILE dbf, avail_block *avblk, size_t size) |
36 | 1.97k | { |
37 | 1.97k | int rc; |
38 | | |
39 | 1.97k | rc = _gdbm_full_read (dbf, avblk, size); |
40 | 1.97k | if (rc) |
41 | 1.30k | { |
42 | 1.30k | GDBM_DEBUG (GDBM_DEBUG_ERR|GDBM_DEBUG_OPEN, |
43 | 1.30k | "%s: error reading av_table: %s", |
44 | 1.30k | dbf->name, gdbm_db_strerror (dbf)); |
45 | 1.30k | } |
46 | 675 | else |
47 | 675 | rc = gdbm_avail_block_validate (dbf, avblk, size); |
48 | 1.97k | return rc; |
49 | 1.97k | } |
50 | | |
51 | | /* Allocate space in the file DBF for a block NUM_BYTES in length. Return |
52 | | the file address of the start of the block. |
53 | | |
54 | | Each hash bucket has a fixed size avail table. We first check this |
55 | | avail table to satisfy the request for space. In most cases we can |
56 | | and this causes changes to be only in the current hash bucket. |
57 | | Allocation is done on a first fit basis from the entries. If a |
58 | | request can not be satisfied from the current hash bucket, then it is |
59 | | satisfied from the file header avail block. If nothing is there that |
60 | | has enough space, another block at the end of the file is allocated |
61 | | and the unused portion is returned to the avail block. This routine |
62 | | "guarantees" that an allocation does not cross a block boundary unless |
63 | | the size is larger than a single block. The avail structure is |
64 | | changed by this routine if a change is needed. If an error occurs, |
65 | | the value of 0 will be returned. */ |
66 | | |
67 | | off_t |
68 | | _gdbm_alloc (GDBM_FILE dbf, int num_bytes) |
69 | 321k | { |
70 | 321k | off_t file_adr; /* The address of the block. */ |
71 | 321k | avail_elem av_el; /* For temporary use. */ |
72 | | |
73 | | /* The current bucket is the first place to look for space. */ |
74 | 321k | av_el = get_elem (num_bytes, dbf->bucket->bucket_avail, |
75 | 321k | &dbf->bucket->av_count); |
76 | | |
77 | | /* If we did not find some space, we have more work to do. */ |
78 | 321k | if (av_el.av_size == 0) |
79 | 155k | { |
80 | | /* If the header avail table is less than half full, and there's |
81 | | something on the stack. */ |
82 | 155k | if ((dbf->avail->count <= (dbf->avail->size >> 1)) |
83 | 62.0k | && (dbf->avail->next_block != 0)) |
84 | 23 | if (pop_avail_block (dbf)) |
85 | 0 | return 0; |
86 | | |
87 | | /* check the header avail table next */ |
88 | 155k | av_el = get_elem (num_bytes, dbf->avail->av_table, &dbf->avail->count); |
89 | 155k | if (av_el.av_size == 0) |
90 | | /* Get another full block from end of file. */ |
91 | 153k | av_el = get_block (num_bytes, dbf); |
92 | | |
93 | 155k | dbf->header_changed = TRUE; |
94 | 155k | } |
95 | | |
96 | | /* We now have the place from which we will allocate the new space. */ |
97 | 321k | file_adr = av_el.av_adr; |
98 | | |
99 | | /* Put the unused space back in the avail block. */ |
100 | 321k | av_el.av_adr += num_bytes; |
101 | 321k | av_el.av_size -= num_bytes; |
102 | 321k | if (_gdbm_free (dbf, av_el.av_adr, av_el.av_size)) |
103 | 0 | return 0; |
104 | | |
105 | | /* Return the address. */ |
106 | 321k | return file_adr; |
107 | | |
108 | 321k | } |
109 | | |
110 | | /* Free space of size NUM_BYTES in the file DBF at file address FILE_ADR. Make |
111 | | it available for reuse through _gdbm_alloc. This routine changes the |
112 | | avail structure. */ |
113 | | |
114 | | int |
115 | | _gdbm_free (GDBM_FILE dbf, off_t file_adr, int num_bytes) |
116 | 325k | { |
117 | 325k | avail_elem temp; |
118 | | |
119 | | /* Is it too small to worry about? */ |
120 | 325k | if (num_bytes <= IGNORE_SIZE) |
121 | 49.0k | return 0; |
122 | | |
123 | | /* Initialize the avail element. */ |
124 | 276k | avail_elem_init (&temp, num_bytes, file_adr); |
125 | | |
126 | | /* Is the freed space large or small? */ |
127 | 276k | if ((num_bytes >= dbf->header->block_size) || dbf->central_free) |
128 | 1.06k | { |
129 | 1.06k | if (dbf->avail->count == dbf->avail->size) |
130 | 1 | { |
131 | 1 | if (push_avail_block (dbf)) |
132 | 0 | return -1; |
133 | 1 | } |
134 | 1.06k | _gdbm_put_av_elem (temp, dbf->avail->av_table, |
135 | 1.06k | &dbf->avail->count, dbf->coalesce_blocks); |
136 | 1.06k | dbf->header_changed = TRUE; |
137 | 1.06k | } |
138 | 274k | else |
139 | 274k | { |
140 | | /* Try to put into the current bucket. */ |
141 | 274k | if (dbf->bucket->av_count < BUCKET_AVAIL) |
142 | 273k | _gdbm_put_av_elem (temp, dbf->bucket->bucket_avail, |
143 | 273k | &dbf->bucket->av_count, dbf->coalesce_blocks); |
144 | 1.43k | else |
145 | 1.43k | { |
146 | 1.43k | if (dbf->avail->count == dbf->avail->size) |
147 | 593 | { |
148 | 593 | if (push_avail_block (dbf)) |
149 | 0 | return -1; |
150 | 593 | } |
151 | 1.43k | _gdbm_put_av_elem (temp, dbf->avail->av_table, |
152 | 1.43k | &dbf->avail->count, dbf->coalesce_blocks); |
153 | 1.43k | dbf->header_changed = TRUE; |
154 | 1.43k | } |
155 | 274k | } |
156 | | |
157 | 276k | if (dbf->header_changed && adjust_bucket_avail (dbf)) |
158 | 0 | return -1; |
159 | | |
160 | | /* All work is done. */ |
161 | 276k | return 0; |
162 | 276k | } |
163 | | |
164 | | |
165 | | |
166 | | /* The following are all utility routines needed by the previous two. */ |
167 | | |
168 | | |
169 | | /* Gets the avail block at the top of the stack and loads it into the |
170 | | active avail block. It does a "free" for itself! This can be (and is) |
171 | | now called even when the avail block is not empty, so we must be |
172 | | smart about things. */ |
173 | | |
174 | | static int |
175 | | pop_avail_block (GDBM_FILE dbf) |
176 | 23 | { |
177 | 23 | off_t file_pos; /* For use with the lseek system call. */ |
178 | 23 | avail_elem new_el; |
179 | 23 | avail_block *new_blk; |
180 | 23 | int index; |
181 | | |
182 | 23 | if (dbf->avail->count == dbf->avail->size) |
183 | 0 | { |
184 | | /* We're kind of stuck here, so we re-split the header in order to |
185 | | avoid crashing. Sigh. */ |
186 | 0 | if (push_avail_block (dbf)) |
187 | 0 | return -1; |
188 | 0 | } |
189 | | |
190 | | /* Set up variables. */ |
191 | 23 | avail_elem_init (&new_el, |
192 | 23 | ( ( (dbf->avail->size * sizeof (avail_elem)) >> 1) |
193 | 23 | + sizeof (avail_block)), |
194 | 23 | dbf->avail->next_block); |
195 | | |
196 | | /* Allocate space for the block. */ |
197 | 23 | new_blk = malloc (new_el.av_size); |
198 | 23 | if (new_blk == NULL) |
199 | 0 | { |
200 | 0 | GDBM_SET_ERRNO (dbf, GDBM_MALLOC_ERROR, TRUE); |
201 | 0 | _gdbm_fatal (dbf, _("malloc failed")); |
202 | 0 | return -1; |
203 | 0 | } |
204 | | |
205 | | /* Read the block. */ |
206 | 23 | file_pos = gdbm_file_seek (dbf, new_el.av_adr, SEEK_SET); |
207 | 23 | if (file_pos != new_el.av_adr) |
208 | 0 | { |
209 | 0 | GDBM_SET_ERRNO (dbf, GDBM_FILE_SEEK_ERROR, TRUE); |
210 | 0 | free (new_blk); |
211 | 0 | _gdbm_fatal (dbf, _("lseek error")); |
212 | 0 | return -1; |
213 | 0 | } |
214 | | |
215 | 23 | if (_gdbm_avail_block_read (dbf, new_blk, new_el.av_size)) |
216 | 0 | { |
217 | 0 | free (new_blk); |
218 | 0 | _gdbm_fatal (dbf, gdbm_db_strerror (dbf)); |
219 | 0 | return -1; |
220 | 0 | } |
221 | | |
222 | | /* Add the elements from the new block to the header. */ |
223 | 23 | index = 0; |
224 | 46 | while (index < new_blk->count) |
225 | 23 | { |
226 | 2.44k | while (index < new_blk->count |
227 | 2.42k | && dbf->avail->count < dbf->avail->size) |
228 | 2.42k | { |
229 | | /* With luck, this will merge a lot of blocks! */ |
230 | 2.42k | _gdbm_put_av_elem (new_blk->av_table[index], |
231 | 2.42k | dbf->avail->av_table, |
232 | 2.42k | &dbf->avail->count, TRUE); |
233 | 2.42k | index++; |
234 | 2.42k | } |
235 | 23 | if (dbf->avail->count == dbf->avail->size) |
236 | 0 | { |
237 | | /* We're kind of stuck here, so we re-split the header in order to |
238 | | avoid crashing. Sigh. */ |
239 | 0 | if (push_avail_block (dbf)) |
240 | 0 | { |
241 | 0 | free (new_blk); |
242 | 0 | return -1; |
243 | 0 | } |
244 | 0 | } |
245 | 23 | } |
246 | | |
247 | | /* Fix next_block, as well. */ |
248 | 23 | dbf->avail->next_block = new_blk->next_block; |
249 | | |
250 | | /* We changed the header. */ |
251 | | /* FIXME: or avail block, when it is separate */ |
252 | 23 | dbf->header_changed = TRUE; |
253 | | |
254 | | /* Free the previous avail block. It is possible that the header table |
255 | | is now FULL, which will cause us to overflow it! */ |
256 | 23 | if (dbf->avail->count == dbf->avail->size) |
257 | 0 | { |
258 | | /* We're kind of stuck here, so we re-split the header in order to |
259 | | avoid crashing. Sigh. */ |
260 | 0 | if (push_avail_block (dbf)) |
261 | 0 | { |
262 | 0 | free (new_blk); |
263 | 0 | return -1; |
264 | 0 | } |
265 | 0 | } |
266 | | |
267 | 23 | _gdbm_put_av_elem (new_el, dbf->avail->av_table, &dbf->avail->count, TRUE); |
268 | 23 | free (new_blk); |
269 | | |
270 | 23 | return 0; |
271 | 23 | } |
272 | | |
273 | | |
274 | | /* Splits the header avail block and pushes half onto the avail stack. */ |
275 | | |
276 | | static int |
277 | | push_avail_block (GDBM_FILE dbf) |
278 | 594 | { |
279 | 594 | int av_size; |
280 | 594 | off_t av_adr; |
281 | 594 | int index; |
282 | 594 | off_t file_pos; |
283 | 594 | avail_block *temp; |
284 | 594 | avail_elem new_loc; |
285 | 594 | int rc; |
286 | | |
287 | | /* Calculate the size of the split block. */ |
288 | 594 | av_size = ( (dbf->avail->size * sizeof (avail_elem)) >> 1) |
289 | 594 | + sizeof (avail_block); |
290 | | |
291 | | /* Get address in file for new av_size bytes. */ |
292 | 594 | new_loc = get_elem (av_size, dbf->avail->av_table, &dbf->avail->count); |
293 | 594 | if (new_loc.av_size == 0) |
294 | 0 | new_loc = get_block (av_size, dbf); |
295 | 594 | av_adr = new_loc.av_adr; |
296 | | |
297 | | /* Split the header block. */ |
298 | 594 | temp = calloc (1, av_size); |
299 | 594 | if (temp == NULL) |
300 | 0 | { |
301 | 0 | GDBM_SET_ERRNO (dbf, GDBM_MALLOC_ERROR, TRUE); |
302 | 0 | _gdbm_fatal (dbf, _("malloc error")); |
303 | 0 | return -1; |
304 | 0 | } |
305 | | |
306 | | /* Set the size to be correct AFTER the pop_avail_block. */ |
307 | 594 | temp->size = dbf->avail->size; |
308 | 594 | temp->count = 0; |
309 | 594 | temp->next_block = dbf->avail->next_block; |
310 | 594 | dbf->avail->next_block = av_adr; |
311 | 115k | for (index = 1; index < dbf->avail->count; index++) |
312 | 114k | if ( (index & 0x1) == 1) /* Index is odd. */ |
313 | 57.4k | temp->av_table[temp->count++] = dbf->avail->av_table[index]; |
314 | 57.4k | else |
315 | 57.4k | dbf->avail->av_table[index>>1] = dbf->avail->av_table[index]; |
316 | | |
317 | | /* Update the header avail count. */ |
318 | 594 | dbf->avail->count -= temp->count; |
319 | | |
320 | 594 | rc = 0; |
321 | 594 | do |
322 | 594 | { |
323 | | /* Free the unneeded space. */ |
324 | 594 | new_loc.av_adr += av_size; |
325 | 594 | new_loc.av_size -= av_size; |
326 | 594 | if (_gdbm_free (dbf, new_loc.av_adr, new_loc.av_size)) |
327 | 0 | { |
328 | 0 | rc = -1; |
329 | 0 | break; |
330 | 0 | } |
331 | | |
332 | | /* Update the disk. */ |
333 | 594 | file_pos = gdbm_file_seek (dbf, av_adr, SEEK_SET); |
334 | 594 | if (file_pos != av_adr) |
335 | 0 | { |
336 | 0 | GDBM_SET_ERRNO (dbf, GDBM_FILE_SEEK_ERROR, TRUE); |
337 | 0 | _gdbm_fatal (dbf, _("lseek error")); |
338 | 0 | rc = -1; |
339 | 0 | break; |
340 | 0 | } |
341 | | |
342 | 594 | rc = _gdbm_full_write (dbf, temp, av_size); |
343 | 594 | if (rc) |
344 | 0 | { |
345 | 0 | GDBM_DEBUG (GDBM_DEBUG_STORE|GDBM_DEBUG_ERR, |
346 | 0 | "%s: error writing avail data: %s", |
347 | 0 | dbf->name, gdbm_db_strerror (dbf)); |
348 | 0 | _gdbm_fatal (dbf, gdbm_db_strerror (dbf)); |
349 | 0 | rc = -1; |
350 | 0 | } |
351 | 594 | } |
352 | 594 | while (0); |
353 | | |
354 | 594 | free (temp); |
355 | | |
356 | 594 | return rc; |
357 | 594 | } |
358 | | |
359 | | /* AV_TABLE contains COUNT entries sorted by AV_SIZE in ascending order. |
360 | | Avail_lookup returns index of an item whose AV_SIZE member is greater |
361 | | than or equal to SIZE. */ |
362 | | static int |
363 | | avail_lookup (int size, avail_elem *av_table, int count) |
364 | 960k | { |
365 | 960k | int start = 0; |
366 | | |
367 | 4.01M | while (count > 0) |
368 | 3.09M | { |
369 | 3.09M | int pivot = start + (count >> 1); |
370 | 3.09M | if (size == av_table[pivot].av_size) |
371 | 42.8k | return pivot; |
372 | 3.05M | if (size > av_table[pivot].av_size) |
373 | 1.61M | { |
374 | 1.61M | start = pivot + 1; |
375 | 1.61M | count--; |
376 | 1.61M | } |
377 | 3.05M | count >>= 1; |
378 | 3.05M | } |
379 | 917k | return start; |
380 | 960k | } |
381 | | |
382 | | /* In array AV_TABLE of *AV_COUNT items, move all items from |
383 | | position SRC to DST and update *AV_COUNT accordingly. |
384 | | */ |
385 | | static inline void |
386 | | avail_move (avail_elem *av_table, int *av_count, int src, int dst) |
387 | 651k | { |
388 | 651k | memmove (av_table + dst, av_table + src, |
389 | 651k | (*av_count - src) * sizeof av_table[0]); |
390 | 651k | *av_count += dst - src; |
391 | 651k | } |
392 | | |
393 | | /* Get_elem returns an element in the AV_TABLE block which is |
394 | | larger than SIZE. AV_COUNT is the number of elements in the |
395 | | AV_TABLE. If an item is found, it extracts it from the AV_TABLE |
396 | | and moves the other elements up to fill the space. If no block is |
397 | | found larger than SIZE, get_elem returns a size of zero. This |
398 | | routine does no I/O. */ |
399 | | |
400 | | static avail_elem |
401 | | get_elem (int size, avail_elem av_table[], int *av_count) |
402 | 577k | { |
403 | 577k | int index; /* For searching through the avail block. */ |
404 | 577k | avail_elem val; /* The default return value. */ |
405 | | |
406 | | /* Initialize default return value. */ |
407 | 577k | avail_elem_init (&val, 0, 0); |
408 | | |
409 | | /* Search for element. List is sorted by size. */ |
410 | 577k | index = avail_lookup (size, av_table, *av_count); |
411 | | |
412 | | /* Did we find one of the right size? */ |
413 | 577k | if (index >= *av_count) |
414 | 309k | return val; |
415 | | |
416 | | /* Ok, save that element and move all others up one. */ |
417 | 268k | val = av_table[index]; |
418 | 268k | avail_move (av_table, av_count, index + 1, index); |
419 | 268k | return val; |
420 | 577k | } |
421 | | |
422 | | /* This routine inserts a single NEW_EL into the AV_TABLE block. |
423 | | This routine does no I/O. */ |
424 | | |
425 | | void |
426 | | _gdbm_put_av_elem (avail_elem new_el, avail_elem av_table[], int *av_count, |
427 | | int can_merge) |
428 | 383k | { |
429 | 383k | int index; |
430 | | |
431 | | /* Is it too small to deal with? */ |
432 | 383k | if (new_el.av_size <= IGNORE_SIZE) |
433 | 0 | return; |
434 | | |
435 | 383k | if (can_merge == TRUE) |
436 | 2.57k | { |
437 | | /* Search for blocks to coalesce with this one. */ |
438 | 2.57k | int i; |
439 | | |
440 | 452k | for (i = 0; i < *av_count;) |
441 | 450k | { |
442 | 450k | if ((av_table[i].av_adr + av_table[i].av_size) == new_el.av_adr) |
443 | 11 | { |
444 | | /* Right adjacent */ |
445 | 11 | new_el.av_size += av_table[i].av_size; |
446 | 11 | new_el.av_adr = av_table[i].av_adr; |
447 | 11 | avail_move (av_table, av_count, i + 1, i); |
448 | 11 | } |
449 | 450k | else if ((new_el.av_adr + new_el.av_size) == av_table[i].av_adr) |
450 | 27 | { |
451 | | /* Left adjacent */ |
452 | 27 | new_el.av_size += av_table[i].av_size; |
453 | 27 | avail_move (av_table, av_count, i + 1, i); |
454 | 27 | } |
455 | 450k | else |
456 | 450k | i++; |
457 | 450k | } |
458 | 2.57k | } |
459 | | |
460 | | /* Search for place to put element. List is sorted by size. */ |
461 | 383k | index = avail_lookup (new_el.av_size, av_table, *av_count); |
462 | | /* Move all others up one. */ |
463 | 383k | avail_move (av_table, av_count, index, index + 1); |
464 | | /* Add the new element. */ |
465 | 383k | av_table[index] = new_el; |
466 | 383k | } |
467 | | |
468 | | /* Get_block "allocates" new file space and the end of the file. This is |
469 | | done in integral block sizes. (This helps ensure that data smaller than |
470 | | one block size is in a single block.) Enough blocks are allocated to |
471 | | make sure the number of bytes allocated in the blocks is larger than SIZE. |
472 | | DBF contains the file header that needs updating. This routine does |
473 | | no I/O. */ |
474 | | |
475 | | static avail_elem |
476 | | get_block (int size, GDBM_FILE dbf) |
477 | 153k | { |
478 | 153k | avail_elem val; |
479 | | |
480 | | /* Need at least one block. */ |
481 | 153k | avail_elem_init (&val, dbf->header->block_size, dbf->header->next_block); |
482 | | |
483 | | /* Get enough blocks to fit the need. */ |
484 | 2.85M | while (val.av_size < size) |
485 | 2.70M | val.av_size += dbf->header->block_size; |
486 | | |
487 | | /* Update the header and return. */ |
488 | 153k | dbf->header->next_block += val.av_size; |
489 | | |
490 | | /* We changed the header. */ |
491 | 153k | dbf->header_changed = TRUE; |
492 | | |
493 | 153k | return val; |
494 | | |
495 | 153k | } |
496 | | |
497 | | |
498 | | /* When the header already needs writing, we can make sure the current |
499 | | bucket has its avail block as close to 1/3 full as possible. */ |
500 | | static int |
501 | | adjust_bucket_avail (GDBM_FILE dbf) |
502 | 116k | { |
503 | 116k | int third = BUCKET_AVAIL / 3; |
504 | 116k | avail_elem av_el; |
505 | | |
506 | | /* Can we add more entries to the bucket? */ |
507 | 116k | if (dbf->bucket->av_count < third) |
508 | 1.11k | { |
509 | 1.11k | if (dbf->avail->count > 0) |
510 | 1.00k | { |
511 | 1.00k | dbf->avail->count -= 1; |
512 | 1.00k | av_el = dbf->avail->av_table[dbf->avail->count]; |
513 | 1.00k | _gdbm_put_av_elem (av_el, dbf->bucket->bucket_avail, |
514 | 1.00k | &dbf->bucket->av_count, dbf->coalesce_blocks); |
515 | 1.00k | _gdbm_current_bucket_changed (dbf); |
516 | 1.00k | } |
517 | 1.11k | return 0; |
518 | 1.11k | } |
519 | | |
520 | | /* Is there too much in the bucket? */ |
521 | 214k | while (dbf->bucket->av_count > BUCKET_AVAIL-third |
522 | 102k | && dbf->avail->count < dbf->avail->size) |
523 | 99.7k | { |
524 | 99.7k | av_el = get_elem (0, dbf->bucket->bucket_avail, &dbf->bucket->av_count); |
525 | 99.7k | if (av_el.av_size == 0) |
526 | 0 | { |
527 | 0 | GDBM_SET_ERRNO (dbf, GDBM_BAD_AVAIL, TRUE); |
528 | 0 | return -1; |
529 | 0 | } |
530 | 99.7k | _gdbm_put_av_elem (av_el, dbf->avail->av_table, |
531 | 99.7k | &dbf->avail->count, |
532 | 99.7k | dbf->coalesce_blocks); |
533 | 99.7k | _gdbm_current_bucket_changed (dbf); |
534 | 99.7k | } |
535 | 115k | return 0; |
536 | 115k | } |