Coverage Report

Created: 2023-06-07 07:16

/src/gdbm/src/falloc.c
Line
Count
Source (jump to first uncovered line)
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-2023 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.50k
{
37
1.50k
  int rc;
38
  
39
1.50k
  rc = _gdbm_full_read (dbf, avblk, size);
40
1.50k
  if (rc)
41
1.13k
    {
42
1.13k
      GDBM_DEBUG (GDBM_DEBUG_ERR|GDBM_DEBUG_OPEN,
43
1.13k
      "%s: error reading av_table: %s",
44
1.13k
      dbf->name, gdbm_db_strerror (dbf));
45
1.13k
    }
46
365
  else
47
365
    rc = gdbm_avail_block_validate (dbf, avblk, size);
48
1.50k
  return rc;
49
1.50k
}
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
246k
{
70
246k
  off_t file_adr;   /* The address of the block. */
71
246k
  avail_elem av_el;   /* For temporary use. */
72
73
  /* The current bucket is the first place to look for space. */
74
246k
  av_el = get_elem (num_bytes, dbf->bucket->bucket_avail,
75
246k
        &dbf->bucket->av_count);
76
77
  /* If we did not find some space, we have more work to do. */
78
246k
  if (av_el.av_size == 0)
79
118k
    {
80
      /* If the header avail table is less than half full, and there's
81
   something on the stack. */
82
118k
      if ((dbf->avail->count <= (dbf->avail->size >> 1))
83
118k
          && (dbf->avail->next_block != 0))
84
25
        if (pop_avail_block (dbf))
85
0
    return 0;
86
87
      /* check the header avail table next */
88
118k
      av_el = get_elem (num_bytes, dbf->avail->av_table, &dbf->avail->count);
89
118k
      if (av_el.av_size == 0)
90
        /* Get another full block from end of file. */
91
115k
        av_el = get_block (num_bytes, dbf);
92
93
118k
      dbf->header_changed = TRUE;
94
118k
    }
95
96
  /* We now have the place from which we will allocate the new space. */
97
246k
  file_adr = av_el.av_adr;
98
99
  /* Put the unused space back in the avail block. */
100
246k
  av_el.av_adr += num_bytes;
101
246k
  av_el.av_size -= num_bytes;
102
246k
  if (_gdbm_free (dbf, av_el.av_adr, av_el.av_size))
103
0
    return 0;
104
105
  /* Return the address. */
106
246k
  return file_adr;
107
  
108
246k
}
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
249k
{
117
249k
  avail_elem temp;
118
119
  /* Is it too small to worry about? */
120
249k
  if (num_bytes <= IGNORE_SIZE)
121
49.9k
    return 0;
122
123
  /* Initialize the avail element. */
124
199k
  temp.av_size = num_bytes;
125
199k
  temp.av_adr = file_adr;
126
127
  /* Is the freed space large or small? */
128
199k
  if ((num_bytes >= dbf->header->block_size) || dbf->central_free)
129
1.05k
    {
130
1.05k
      if (dbf->avail->count == dbf->avail->size)
131
2
  {
132
2
    if (push_avail_block (dbf))
133
0
      return -1;
134
2
  }
135
1.05k
      _gdbm_put_av_elem (temp, dbf->avail->av_table,
136
1.05k
       &dbf->avail->count, dbf->coalesce_blocks);
137
1.05k
      dbf->header_changed = TRUE;
138
1.05k
    }
139
198k
  else
140
198k
    {
141
      /* Try to put into the current bucket. */
142
198k
      if (dbf->bucket->av_count < BUCKET_AVAIL)
143
197k
  _gdbm_put_av_elem (temp, dbf->bucket->bucket_avail,
144
197k
         &dbf->bucket->av_count, dbf->coalesce_blocks);
145
358
      else
146
358
  {
147
358
    if (dbf->avail->count == dbf->avail->size)
148
234
      {
149
234
        if (push_avail_block (dbf))
150
0
    return -1;
151
234
      }
152
358
    _gdbm_put_av_elem (temp, dbf->avail->av_table,
153
358
           &dbf->avail->count, dbf->coalesce_blocks);
154
358
    dbf->header_changed = TRUE;
155
358
  }
156
198k
    }
157
158
199k
  if (dbf->header_changed && adjust_bucket_avail (dbf))
159
0
    return -1;
160
161
  /* All work is done. */
162
199k
  return 0;
163
199k
}
164
165
166
167
/* The following are all utility routines needed by the previous two. */
168
169
170
/* Gets the avail block at the top of the stack and loads it into the
171
   active avail block.  It does a "free" for itself!  This can be (and is)
172
   now called even when the avail block is not empty, so we must be
173
   smart about things. */
174
175
static int
176
pop_avail_block (GDBM_FILE dbf)
177
25
{
178
25
  off_t file_pos;   /* For use with the lseek system call. */
179
25
  avail_elem new_el;
180
25
  avail_block *new_blk;
181
25
  int index;
182
  
183
25
  if (dbf->avail->count == dbf->avail->size)
184
0
    {
185
      /* We're kind of stuck here, so we re-split the header in order to
186
         avoid crashing.  Sigh. */
187
0
      if (push_avail_block (dbf))
188
0
  return -1;
189
0
    }
190
191
  /* Set up variables. */
192
25
  new_el.av_adr = dbf->avail->next_block;
193
25
  new_el.av_size = ( ( (dbf->avail->size * sizeof (avail_elem)) >> 1)
194
25
      + sizeof (avail_block));
195
196
  /* Allocate space for the block. */
197
25
  new_blk = malloc (new_el.av_size);
198
25
  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
25
  file_pos = gdbm_file_seek (dbf, new_el.av_adr, SEEK_SET);
207
25
  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
25
  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
25
  index = 0;
224
50
  while (index < new_blk->count)
225
25
    {
226
2.03k
      while (index < new_blk->count
227
2.03k
       && dbf->avail->count < dbf->avail->size)
228
2.00k
  {
229
     /* With luck, this will merge a lot of blocks! */
230
2.00k
     _gdbm_put_av_elem (new_blk->av_table[index],
231
2.00k
            dbf->avail->av_table,
232
2.00k
            &dbf->avail->count, TRUE);
233
2.00k
     index++;
234
2.00k
  }
235
25
      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
25
    }
246
247
  /* Fix next_block, as well. */
248
25
  dbf->avail->next_block = new_blk->next_block;
249
250
  /* We changed the header. */
251
  //FIXME: or avail block, when it is separate
252
25
  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
25
  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
25
  _gdbm_put_av_elem (new_el, dbf->avail->av_table, &dbf->avail->count, TRUE);
268
25
  free (new_blk);
269
270
25
  return 0;
271
25
}
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
236
{
279
236
  int  av_size;
280
236
  off_t av_adr;
281
236
  int  index;
282
236
  off_t file_pos;
283
236
  avail_block *temp;
284
236
  avail_elem  new_loc;
285
236
  int rc;
286
287
  /* Caclulate the size of the split block. */
288
236
  av_size = ( (dbf->avail->size * sizeof (avail_elem)) >> 1)
289
236
            + sizeof (avail_block);
290
291
  /* Get address in file for new av_size bytes. */
292
236
  new_loc = get_elem (av_size, dbf->avail->av_table, &dbf->avail->count);
293
236
  if (new_loc.av_size == 0)
294
0
    new_loc = get_block (av_size, dbf);
295
236
  av_adr = new_loc.av_adr;
296
297
  /* Split the header block. */
298
236
  temp = calloc (1, av_size);
299
236
  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
236
  temp->size = dbf->avail->size;
308
236
  temp->count = 0;
309
236
  temp->next_block = dbf->avail->next_block;
310
236
  dbf->avail->next_block = av_adr;
311
41.0k
  for (index = 1; index < dbf->avail->count; index++)
312
40.8k
    if ( (index & 0x1) == 1)  /* Index is odd. */
313
20.4k
      temp->av_table[temp->count++] = dbf->avail->av_table[index];
314
20.4k
    else
315
20.4k
      dbf->avail->av_table[index>>1] = dbf->avail->av_table[index];
316
317
  /* Update the header avail count. */
318
236
  dbf->avail->count -= temp->count;
319
320
236
  rc = 0;
321
236
  do
322
236
    {
323
      /* Free the unneeded space. */
324
236
      new_loc.av_adr += av_size;
325
236
      new_loc.av_size -= av_size;
326
236
      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
236
      file_pos = gdbm_file_seek (dbf, av_adr, SEEK_SET);
334
236
      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
236
      rc = _gdbm_full_write (dbf, temp, av_size);
343
236
      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
236
    }
352
236
  while (0);
353
  
354
0
  free (temp);
355
356
236
  return rc;
357
236
}
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
696k
{
365
696k
  int start = 0;
366
  
367
2.70M
  while (count > 0)
368
2.04M
    {
369
2.04M
      int pivot = start + (count >> 1);
370
2.04M
      if (size == av_table[pivot].av_size)
371
33.7k
  return pivot;
372
2.00M
      if (size > av_table[pivot].av_size)
373
1.07M
  {
374
1.07M
    start = pivot + 1;
375
1.07M
    count--;
376
1.07M
  }
377
2.00M
      count >>= 1;
378
2.00M
    }
379
662k
  return start;
380
696k
}
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
462k
{
388
462k
  memmove (av_table + dst, av_table + src,
389
462k
     (*av_count - src) * sizeof av_table[0]);
390
462k
  *av_count += dst - src;
391
462k
}
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
427k
{
403
427k
  int index;      /* For searching through the avail block. */
404
427k
  avail_elem val;   /* The default return value. */
405
406
  /* Initialize default return value. */
407
427k
  val.av_adr = 0;
408
427k
  val.av_size = 0;
409
410
  /* Search for element.  List is sorted by size. */
411
427k
  index = avail_lookup (size, av_table, *av_count);
412
413
  /* Did we find one of the right size? */
414
427k
  if (index >= *av_count)
415
234k
    return val;
416
417
  /* Ok, save that element and move all others up one. */
418
193k
  val = av_table[index];
419
193k
  avail_move (av_table, av_count, index + 1, index);
420
193k
  return val;
421
427k
}
422
423
/* This routine inserts a single NEW_EL into the AV_TABLE block.
424
   This routine does no I/O. */
425
426
void
427
_gdbm_put_av_elem (avail_elem new_el, avail_elem av_table[], int *av_count,
428
           int can_merge)
429
268k
{
430
268k
  int index;
431
432
  /* Is it too small to deal with? */
433
268k
  if (new_el.av_size <= IGNORE_SIZE)
434
0
    return;
435
436
268k
  if (can_merge == TRUE)
437
2.15k
    {
438
      /* Search for blocks to coalesce with this one. */
439
2.15k
      int i;
440
      
441
361k
      for (i = 0; i < *av_count; i++)
442
359k
  {
443
359k
    if ((av_table[i].av_adr + av_table[i].av_size) == new_el.av_adr)
444
6
      {
445
        /* Right adjacent */
446
6
        new_el.av_size += av_table[i].av_size;
447
6
        new_el.av_adr = av_table[i].av_adr;
448
6
        avail_move (av_table, av_count, i + 1, i);
449
6
        --i;
450
6
      }
451
452
359k
    if ((new_el.av_adr + new_el.av_size) == av_table[i].av_adr)
453
17
      {
454
        /* Left adjacent */
455
17
        new_el.av_size += av_table[i].av_size;
456
17
        avail_move (av_table, av_count, i + 1, i);
457
17
        --i;
458
17
      }
459
359k
  }
460
2.15k
    }
461
462
  /* Search for place to put element.  List is sorted by size. */
463
268k
  index = avail_lookup (new_el.av_size, av_table, *av_count);
464
  /* Move all others up one. */
465
268k
  avail_move (av_table, av_count, index, index + 1);
466
  /* Add the new element. */
467
268k
  av_table[index] = new_el;
468
268k
}
469
470
/* Get_block "allocates" new file space and the end of the file.  This is
471
   done in integral block sizes.  (This helps ensure that data smaller than
472
   one block size is in a single block.)  Enough blocks are allocated to
473
   make sure the number of bytes allocated in the blocks is larger than SIZE.
474
   DBF contains the file header that needs updating.  This routine does
475
   no I/O.  */
476
477
static avail_elem
478
get_block (int size, GDBM_FILE dbf)
479
115k
{
480
115k
  avail_elem val;
481
482
  /* Need at least one block. */
483
115k
  val.av_adr  = dbf->header->next_block;
484
115k
  val.av_size = dbf->header->block_size;
485
486
  /* Get enough blocks to fit the need. */
487
3.34M
  while (val.av_size < size)
488
3.22M
    val.av_size += dbf->header->block_size;
489
490
  /* Update the header and return. */
491
115k
  dbf->header->next_block += val.av_size;
492
493
  /* We changed the header. */
494
115k
  dbf->header_changed = TRUE;
495
496
115k
  return val;
497
  
498
115k
}
499
500
501
/*  When the header already needs writing, we can make sure the current
502
    bucket has its avail block as close to 1/3 full as possible. */
503
static int
504
adjust_bucket_avail (GDBM_FILE dbf)
505
77.7k
{
506
77.7k
  int third = BUCKET_AVAIL / 3;
507
77.7k
  avail_elem av_el;
508
509
  /* Can we add more entries to the bucket? */
510
77.7k
  if (dbf->bucket->av_count < third)
511
1.20k
    {
512
1.20k
      if (dbf->avail->count > 0)
513
1.02k
  {
514
1.02k
    dbf->avail->count -= 1;
515
1.02k
    av_el = dbf->avail->av_table[dbf->avail->count];
516
1.02k
    _gdbm_put_av_elem (av_el, dbf->bucket->bucket_avail,
517
1.02k
           &dbf->bucket->av_count, dbf->coalesce_blocks);
518
1.02k
    _gdbm_current_bucket_changed (dbf);
519
1.02k
  }
520
1.20k
      return 0;
521
1.20k
    }
522
523
  /* Is there too much in the bucket? */
524
139k
  while (dbf->bucket->av_count > BUCKET_AVAIL-third
525
139k
   && dbf->avail->count < dbf->avail->size)
526
63.0k
    {
527
63.0k
      av_el = get_elem (0, dbf->bucket->bucket_avail, &dbf->bucket->av_count);
528
63.0k
      if (av_el.av_size == 0)
529
0
  {
530
0
    GDBM_SET_ERRNO (dbf, GDBM_BAD_AVAIL, TRUE);
531
0
    return -1;
532
0
  }
533
63.0k
      _gdbm_put_av_elem (av_el, dbf->avail->av_table,
534
63.0k
       &dbf->avail->count,
535
63.0k
       dbf->coalesce_blocks);
536
63.0k
      _gdbm_current_bucket_changed (dbf);
537
63.0k
    }
538
76.5k
  return 0;
539
76.5k
}