Coverage Report

Created: 2026-10-17 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdbm/src/falloc.c
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
}