• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

proftpd / proftpd / 26127302554

19 May 2026 07:34PM UTC coverage: 92.635% (-0.4%) from 93.024%
26127302554

push

github

Castaglia
Make a flaky, racy mod_copy regression test more reliable.

47303 of 51064 relevant lines covered (92.63%)

241.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.69
/src/table.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2004-2026 The ProFTPD Project team
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
17
 *
18
 * As a special exemption, The ProFTPD Project team and other respective
19
 * copyright holders give permission to link this program with OpenSSL, and
20
 * distribute the resulting executable, without including the source code for
21
 * OpenSSL in the source distribution.
22
 */
23

24
/* Table API implementation */
25

26
#include "conf.h"
27

28
#ifdef PR_USE_OPENSSL
29
#include <openssl/rand.h>
30
#endif /* PR_USE_OPENSSL */
31

32
#define PR_TABLE_DEFAULT_NCHAINS        256
33
#define PR_TABLE_DEFAULT_MAX_ENTS        8192
34
#define PR_TABLE_ENT_POOL_SIZE                64
35

36
struct table_rec {
37
  pool *pool;
38
  unsigned long flags;
39

40
  /* These bytes are randomly generated at table creation time, and
41
   * are used to seed the hashing function, so as to defend/migitate
42
   * against attempts to feed carefully crafted keys which force the
43
   * table into its worst-case performance scenario.
44
   *
45
   * For more information on attacks of this nature, see:
46
   *
47
   *   http://www.cs.rice.edu/~scrosby/hash/CrosbyWallach_UsenixSec2003/
48
   */
49
  unsigned int seed;
50

51
  /* Maximum number of entries that can be stored in this table.  The
52
   * default maximum (PR_TABLE_DEFAULT_MAX_ENTS) is set fairly high.
53
   * This limit is present in order to defend/mitigate against certain abuse
54
   * scenarios.
55
   *
56
   * XXX Note that an additional protective measure can/might be placed on
57
   * the maximum length of a given chain, to detect other types of attacks
58
   * that force the table into the worse-case performance scenario (i.e.
59
   * linear scanning of a long chain).  If such is added, then a Table API
60
   * function should be added for returning the length of the longest chain
61
   * in the table.  Such a function could be used by modules to determine
62
   * if their tables are being abused (or in need of readjustment).
63
   */
64
  unsigned int nmaxents;
65

66
  pr_table_entry_t **chains;
67
  unsigned int nchains;
68
  unsigned int nents;
69

70
  /* List of free structures. */
71
  pr_table_entry_t *free_ents;
72
  pr_table_key_t *free_keys;
73

74
  /* For iterating over all the keys in the entire table. */
75
  pr_table_entry_t *tab_iter_ent;
76

77
  /* For iterating through all of the possible multiple values for a single
78
   * key.  Only used if the PR_TABLE_FL_MULTI_VALUE flag is set.
79
   */
80
  pr_table_entry_t *val_iter_ent;
81

82
  /* Cache of last looked-up entry.  Usage of this field can be enabled
83
   * by using the PR_TABLE_FL_USE_CACHE flag.
84
   */
85
  pr_table_entry_t *cache_ent;
86

87
  /* Table callbacks. */
88
  int (*keycmp)(const void *, size_t, const void *, size_t);
89
  unsigned int (*keyhash)(const void *, size_t);
90
  void (*entinsert)(pr_table_entry_t **, pr_table_entry_t *);
91
  void (*entremove)(pr_table_entry_t **, pr_table_entry_t *);
92
};
93

94
static int handling_signal = FALSE;
95

96
static const char *trace_channel = "table";
97

98
/* Default table callbacks
99
 */
100

101
static int key_cmp(const void *key1, size_t keysz1, const void *key2,
8,214✔
102
    size_t keysz2) {
103
  const char *k1, *k2;
104

105
  if (keysz1 != keysz2) {
8,214✔
106
    return keysz1 < keysz2 ? -1 : 1;
×
107
  }
108

109
  k1 = key1;
8,214✔
110
  k2 = key2;
8,214✔
111

112
  if (keysz1 >= 1) {
8,214✔
113
    /* Basic check of the first character in each key, trying to reduce
114
     * the chances of calling strncmp(3) if not needed.
115
     */
116

117
    if (k1[0] != k2[0]) {
8,214✔
118
      return k1[0] < k2[0] ? -1 : 1;
×
119
    }
120

121
    /* Special case (unlikely, but possible). */
122
    if (keysz1 == 1) {
8,214✔
123
      return 0;
124
    }
125
  }
126

127
  return strncmp((const char *) key1, (const char *) key2, keysz1);
8,213✔
128
}
129

130
/* Use Perl's hashing algorithm by default.
131
 *
132
 * Here's a good article about this hashing algorithm, and about hashing
133
 * functions in general:
134
 *
135
 *  http://www.perl.com/pub/2002/10/01/hashes.html
136
 */
137
static unsigned int key_hash(const void *key, size_t keysz) {
12,498✔
138
  unsigned int i = 0;
12,498✔
139
  size_t sz = !keysz ? strlen((const char *) key) : keysz;
12,498✔
140

141
  while (sz--) {
93,681✔
142
    const char *k = key;
81,183✔
143
    unsigned int c;
144

145
    c = k[sz];
81,183✔
146

147
    if (!handling_signal) {
81,183✔
148
      /* Always handle signals in potentially long-running while loops. */
149
      pr_signals_handle();
81,183✔
150
    }
151

152
    i = (i * 33) + c;
81,183✔
153
  }
154

155
  return i;
12,498✔
156
}
157

158
/* Default insertion is simply to add the given entry to the end of the
159
 * chain.
160
 */
161
static void entry_insert(pr_table_entry_t **h, pr_table_entry_t *e) {
116✔
162
  pr_table_entry_t *ei;
163

164
  if (*h == NULL) {
116✔
165
    return;
166
  }
167

168
  for (ei = *h; ei != NULL && ei->next; ei = ei->next) {
2,978✔
169
  }
170

171
  /* Now, ei points to the last entry in the chain. */
172
  ei->next = e;
116✔
173
  e->prev = ei;
116✔
174
}
175

176
/* Default removal is simply to remove the entry from the chain. */
177
static void entry_remove(pr_table_entry_t **h, pr_table_entry_t *e) {
85✔
178

179
  if (e->next) {
85✔
180
    e->next->prev = e->prev;
1✔
181
  }
182

183
  if (e->prev) {
85✔
184
    e->prev->next = e->next;
×
185

186
  } else {
187
    /* This entry is the head. */
188
    *h = e->next;
85✔
189
  }
190

191
  e->prev = e->next = NULL;
85✔
192
}
85✔
193

194
/* Table key management
195
 */
196

197
static pr_table_key_t *tab_key_alloc(pr_table_t *tab) {
198
  pr_table_key_t *k;
199

200
  /* Try to find a free key on the free list first... */
201
  if (tab->free_keys) {
1,998✔
202
    k = tab->free_keys;
38✔
203
    tab->free_keys = k->next;
38✔
204
    k->next = NULL;
38✔
205

206
    return k;
207
  }
208

209
  /* ...otherwise, allocate a new key. */
210
  k = pcalloc(tab->pool, sizeof(pr_table_key_t));
1,960✔
211

212
  return k;
213
}
214

215
static void tab_key_free(pr_table_t *tab, pr_table_key_t *k) {
85✔
216
  /* Clear everything from the given key. */
217
  memset(k, 0, sizeof(pr_table_key_t));
85✔
218

219
  /* Add this key to the table's free list. */
220
  if (tab->free_keys) {
85✔
221
    pr_table_key_t *i = tab->free_keys;
222

223
    /* Scan to the end of the list. */
224
    while (i->next != NULL) {
7✔
225
      if (!handling_signal) {
×
226
        pr_signals_handle();
×
227
      }
228

229
      i = i->next;
×
230
    }
231

232
    i->next = k;
7✔
233

234
  } else {
235
    tab->free_keys = k;
78✔
236
  }
237
}
85✔
238

239
/* Table entry management
240
 */
241

242
static pr_table_entry_t *tab_entry_alloc(pr_table_t *tab) {
243
  pr_table_entry_t *e;
244

245
  /* Try to find a free entry on the free list first... */
246
  if (tab->free_ents) {
2,008✔
247
    e = tab->free_ents;
38✔
248
    tab->free_ents = e->next;
38✔
249
    e->next = NULL;
38✔
250

251
    return e;
252
  }
253

254
  /* ...otherwise, allocate a new entry. */
255
  e = pcalloc(tab->pool, sizeof(pr_table_entry_t));
1,970✔
256

257
  return e;
258
}
259

260
static void tab_entry_free(pr_table_t *tab, pr_table_entry_t *e) {
85✔
261
  /* Clear everything from the given entry. */
262
  memset(e, 0, sizeof(pr_table_entry_t));
85✔
263

264
  /* Add this entry to the table's free list. */
265
  if (tab->free_ents) {
85✔
266
    pr_table_entry_t *i = tab->free_ents;
267

268
    /* Scan to the end of the list. */
269
    while (i->next != NULL) {
7✔
270
      if (!handling_signal) {
×
271
        pr_signals_handle();
×
272
      }
273

274
      i = i->next;
×
275
    }
276

277
    i->next = e;
7✔
278

279
  } else {
280
    tab->free_ents = e;
78✔
281
  }
282
}
85✔
283

284
static void tab_entry_insert(pr_table_t *tab, pr_table_entry_t *e) {
1,999✔
285
  pr_table_entry_t *h = tab->chains[e->idx];
1,999✔
286

287
  if (h &&
3,998✔
288
      h != e) {
1,999✔
289

290
    /* Only insert the entry if the head we found is different from the
291
     * the given entry.  There is an edge case when the entry being added
292
     * is the head of a new chain.
293
     */
294
    tab->entinsert(&h, e);
116✔
295
    tab->chains[e->idx] = h;
116✔
296
  }
297

298
  e->key->nents++;
1,999✔
299
  tab->nents++;
1,999✔
300
}
1,999✔
301

302
static pr_table_entry_t *tab_entry_next(pr_table_t *tab) {
23✔
303
  pr_table_entry_t *ent = NULL;
23✔
304

305
  if (tab->tab_iter_ent) {
23✔
306
    ent = tab->tab_iter_ent->next;
10✔
307

308
    if (!ent) {
10✔
309
      register unsigned int i;
310

311
      /* Reset ent to be NULL, so that if we don't find a populated chain,
312
       * we properly return NULL to the caller.
313
       */
314
      ent = NULL;
10✔
315

316
      /* Skip to the next populated chain. */
317
      for (i = tab->tab_iter_ent->idx + 1; i < tab->nchains; i++) {
696✔
318
        if (tab->chains[i]) {
687✔
319
          ent = tab->chains[i];
320
          break;
321
        }
322
      }
323
    }
324

325
  } else {
326
    register unsigned int i;
327

328
    /* Find the first non-empty chain. */
329
    for (i = 0; i < tab->nchains; i++) {
2,494✔
330
      if (tab->chains[i]) {
2,505✔
331
        ent = tab->chains[i];
332
        break;
333
      }
334
    }
335
  }
336

337
  tab->tab_iter_ent = ent;
23✔
338
  return ent;
23✔
339
}
340

341
static void tab_entry_remove(pr_table_t *tab, pr_table_entry_t *e) {
85✔
342
  pr_table_entry_t *h = NULL;
85✔
343

344
  h = tab->chains[e->idx];
85✔
345
  tab->entremove(&h, e);
85✔
346
  tab->chains[e->idx] = h;
85✔
347

348
  e->key->nents--;
85✔
349
  if (e->key->nents == 0) {
85✔
350
    tab_key_free(tab, e->key);
85✔
351
    e->key = NULL;
85✔
352
  }
353

354
  tab->nents--;
85✔
355
}
85✔
356

357
static unsigned int tab_get_seed(void) {
358
  unsigned int seed = 0;
2,038✔
359
#ifndef PR_USE_OPENSSL
360
  int fd = -1;
361
  ssize_t nread = 0;
362
#endif /* Not PR_USE_OPENSSL */
363

364
#ifdef PR_USE_OPENSSL
365
  RAND_bytes((unsigned char *) &seed, sizeof(seed));
2,038✔
366
#else
367
  /* Try reading from /dev/urandom, if present.  Use non-blocking IO, so that
368
   * we do not block/wait; many platforms alias /dev/urandom to /dev/random.
369
   */
370
  fd = open("/dev/urandom", O_RDONLY|O_NONBLOCK);
371
  if (fd >= 0) {
372
    nread = read(fd, &seed, sizeof(seed));
373
    (void) close(fd);
374
  }
375

376
  if (nread < 0) {
377
    time_t now = time(NULL);
378

379
    /* No /dev/urandom present (e.g. we might be in a chroot) or not able
380
     * to read the needed amount of data, but we still want a seed.  Fallback
381
     * to using rand(3), PID, and current time.
382
     */
383
    seed = (unsigned int) (rand() ^ getpid() ^ now);
384
  }
385
#endif /* PR_USE_OPENSSL */
386

387
  return seed;
2,038✔
388
}
389

390
/* Public Table API
391
 */
392

393
int pr_table_kadd(pr_table_t *tab, const void *key_data, size_t key_datasz,
2,010✔
394
    const void *value_data, size_t value_datasz) {
395
  unsigned int h = 0, idx = 0;
2,010✔
396
  pr_table_entry_t *e = NULL, *n = NULL;
2,010✔
397

398
  if (tab == NULL ||
4,020✔
399
      key_data == NULL ||
4,020✔
400
      (value_datasz > 0 && value_data == NULL)) {
2,010✔
401
    errno = EINVAL;
1✔
402
    return -1;
1✔
403
  }
404

405
  if (tab->nents == tab->nmaxents) {
2,009✔
406
    /* Table is full. */
407
    errno = ENOSPC;
1✔
408
    return -1;
1✔
409
  }
410

411
  /* Don't forget to add in the random seed data. */
412
  h = tab->keyhash(key_data, key_datasz) + tab->seed;
2,008✔
413

414
  /* The index of the chain to use is the hash value modulo the number
415
   * of chains.
416
   */
417
  idx = h % tab->nchains;
2,008✔
418

419
  /* Allocate a new entry for the given values. */
420
  n = tab_entry_alloc(tab);
4,016✔
421
  n->value_data = value_data;
2,008✔
422
  n->value_datasz = value_datasz;
2,008✔
423
  n->idx = idx;
2,008✔
424

425
  /* Find the current chain entry at this index. */
426
  e = tab->chains[idx];
2,008✔
427
  if (e != NULL) {
2,008✔
428
    pr_table_entry_t *ei;
429

430
    /* There is a chain at this index.  Next step is to see if any entry
431
     * on this chain has the exact same key.  If so, increase the entry ref
432
     * count on that key, otherwise, create a new key.
433
     */
434

435
    for (ei = e; ei; ei = ei->next) {
2,978✔
436
      if (ei->key->hash != h) {
2,987✔
437
        continue;
2,977✔
438
      }
439

440
      /* Hash collision.  Now check if the key data that was hashed
441
       * is identical.  If so, we have multiple values for the same key.
442
       */
443

444
      if (tab->keycmp(ei->key->key_data, ei->key->key_datasz,
10✔
445
          key_data, key_datasz) == 0) {
446

447
        /* Check if this table allows multivalues. */
448
        if (!(tab->flags & PR_TABLE_FL_MULTI_VALUE)) {
10✔
449
          errno = EEXIST;
9✔
450
          return -1;
9✔
451
        }
452

453
        n->key = ei->key;
1✔
454
      }
455
    }
456

457
  } else {
458

459
    /* This new entry becomes the head of this chain. */
460
    tab->chains[idx] = n;
1,883✔
461
  }
462

463
  if (!n->key) {
1,999✔
464
    pr_table_key_t *k;
465

466
    /* Allocate a new key. */
467
    k = tab_key_alloc(tab);
3,996✔
468

469
    k->key_data = (void *) key_data;
1,998✔
470
    k->key_datasz = key_datasz;
1,998✔
471
    k->hash = h;
1,998✔
472
    k->nents = 0;
1,998✔
473

474
    n->key = k;
1,998✔
475
  }
476

477
  tab_entry_insert(tab, n);
1,999✔
478
  return 0;
1,999✔
479
}
480

481
int pr_table_kexists(pr_table_t *tab, const void *key_data, size_t key_datasz) {
2,693✔
482
  unsigned int h, idx;
483
  pr_table_entry_t *head, *ent;
484

485
  if (tab == NULL ||
5,386✔
486
      key_data == NULL) {
2,693✔
487
    errno = EINVAL;
2✔
488
    return -1;
2✔
489
  }
490

491
  if (tab->nents == 0) {
2,691✔
492
    errno = ENOENT;
874✔
493
    return -1;
874✔
494
  }
495

496
  if (tab->flags & PR_TABLE_FL_USE_CACHE) {
1,817✔
497
    /* Has the caller already wanted to lookup this same key previously?
498
     * If so, reuse that lookup if we can.  In this case, "same key" means
499
     * the _exact same pointer_, not identical data.
500
     */
501
    if (tab->cache_ent &&
×
502
        tab->cache_ent->key->key_data == key_data) {
×
503
      return tab->cache_ent->key->nents;
×
504
    }
505
  }
506

507
  /* Don't forget to add in the random seed data. */
508
  h = tab->keyhash(key_data, key_datasz) + tab->seed;
1,817✔
509

510
  idx = h % tab->nchains;
1,817✔
511
  head = tab->chains[idx];
1,817✔
512
  if (head == NULL) {
1,817✔
513
    tab->cache_ent = NULL;
546✔
514
    return 0;
546✔
515
  }
516

517
  for (ent = head; ent; ent = ent->next) {
6✔
518
    if (ent->key == NULL ||
2,548✔
519
        ent->key->hash != h) {
1,274✔
520
      continue;
6✔
521
    }
522

523
    /* Matching hashes.  Now to see if the keys themselves match. */
524
    if (tab->keycmp(ent->key->key_data, ent->key->key_datasz,
1,268✔
525
        key_data, key_datasz) == 0) {
526

527
      if (tab->flags & PR_TABLE_FL_USE_CACHE) {
1,268✔
528
        tab->cache_ent = ent;
×
529
      }
530

531
      return ent->key->nents;
1,268✔
532
    }
533
  }
534

535
  tab->cache_ent = NULL;
3✔
536

537
  errno = EINVAL;
3✔
538
  return 0;
3✔
539
}
540

541
const void *pr_table_kget(pr_table_t *tab, const void *key_data,
8,339✔
542
    size_t key_datasz, size_t *value_datasz) {
543
  unsigned int h = 0;
8,339✔
544
  pr_table_entry_t *head = NULL, *ent = NULL;
8,339✔
545

546
  if (tab == NULL) {
8,339✔
547
    errno = EINVAL;
1✔
548
    return NULL;
1✔
549
  }
550

551
  /* Use a NULL key as a way of rewinding the per-key lookup. */
552
  if (key_data == NULL) {
8,338✔
553
    tab->cache_ent = NULL;
4✔
554
    tab->val_iter_ent = NULL;
4✔
555

556
    errno = ENOENT;
4✔
557
    return NULL;
4✔
558
  }
559

560
  if (tab->nents == 0) {
8,334✔
561
    tab->cache_ent = NULL;
814✔
562
    tab->val_iter_ent = NULL;
814✔
563

564
    errno = ENOENT;
814✔
565
    return NULL;
814✔
566
  }
567

568
  /* Don't forget to add in the random seed data. */
569
  h = tab->keyhash(key_data, key_datasz) + tab->seed;
7,520✔
570

571
  /* Has the caller already looked up this same key previously?
572
   * If so, continue the lookup where we left off.  In this case,
573
   * "same key" means the _exact same pointer_, not identical data.
574
   */
575
  if (tab->val_iter_ent &&
7,520✔
576
      tab->val_iter_ent->key->key_data == key_data) {
×
577
    head = tab->val_iter_ent->next;
×
578

579
  } else if ((tab->flags & PR_TABLE_FL_USE_CACHE) &&
7,523✔
580
             tab->cache_ent &&
4✔
581
             tab->cache_ent->key->key_data == key_data) {
1✔
582

583
     /* If the cached lookup entry matches, we'll use it. */
584
     head = tab->cache_ent->next;
1✔
585

586
  } else {
587
    unsigned int idx = h % tab->nchains;
7,519✔
588
    head = tab->chains[idx];
7,519✔
589
  }
590

591
  if (head == NULL) {
7,520✔
592
    tab->cache_ent = NULL;
1,856✔
593
    tab->val_iter_ent = NULL;
1,856✔
594

595
    errno = ENOENT;
1,856✔
596
    return NULL;
1,856✔
597
  }
598

599
  for (ent = head; ent; ent = ent->next) {
88✔
600
    if (ent->key == NULL ||
11,494✔
601
        ent->key->hash != h) {
5,747✔
602
      continue;
88✔
603
    }
604

605
    /* Matching hashes.  Now to see if the keys themselves match. */
606
    if (tab->keycmp(ent->key->key_data, ent->key->key_datasz,
5,659✔
607
        key_data, key_datasz) == 0) {
608

609
      if (tab->flags & PR_TABLE_FL_USE_CACHE) {
5,659✔
610
        tab->cache_ent = ent;
2✔
611
      }
612

613
      if (tab->flags & PR_TABLE_FL_MULTI_VALUE) {
5,659✔
614
        tab->val_iter_ent = ent;
×
615
      }
616

617
      if (value_datasz) {
5,659✔
618
        *value_datasz = ent->value_datasz;
9✔
619
      }
620

621
      return ent->value_data;
5,659✔
622
    }
623
  }
624

625
  tab->cache_ent = NULL;
5✔
626
  tab->val_iter_ent = NULL;
5✔
627

628
  errno = ENOENT;
5✔
629
  return NULL;
5✔
630
}
631

632
const void *pr_table_kremove(pr_table_t *tab, const void *key_data,
78✔
633
    size_t key_datasz, size_t *value_datasz) {
634
  unsigned int h = 0, idx = 0;
78✔
635
  pr_table_entry_t *head = NULL, *ent = NULL;
78✔
636

637
  if (tab == NULL ||
156✔
638
      key_data == NULL) {
78✔
639
    errno = EINVAL;
2✔
640
    return NULL;
2✔
641
  }
642

643
  if (tab->nents == 0) {
76✔
644
    errno = ENOENT;
14✔
645
    return NULL;
14✔
646
  }
647

648
  /* Has the caller already wanted to lookup this same key previously?
649
   * If so, reuse that lookup if we can.  In this case, "same key" means
650
   * the _exact same pointer_, not identical data.
651
   */
652
  if ((tab->flags & PR_TABLE_FL_USE_CACHE) &&
63✔
653
      tab->cache_ent &&
2✔
654
      tab->cache_ent->key->key_data == key_data) {
1✔
655
    const void *value_data;
656

657
    value_data = tab->cache_ent->value_data;
1✔
658
    if (value_datasz) {
1✔
659
      *value_datasz = tab->cache_ent->value_datasz;
×
660
    }
661

662
    tab_entry_remove(tab, tab->cache_ent);
1✔
663
    tab_entry_free(tab, tab->cache_ent);
1✔
664
    tab->cache_ent = NULL;
1✔
665

666
    return value_data;
1✔
667
  }
668

669
  /* Don't forget to add in the random seed data. */
670
  h = tab->keyhash(key_data, key_datasz) + tab->seed;
61✔
671

672
  idx = h % tab->nchains;
61✔
673
  head = tab->chains[idx];
61✔
674
  if (head == NULL) {
61✔
675
    tab->cache_ent = NULL;
×
676

677
    errno = ENOENT;
×
678
    return NULL;
×
679
  }
680

681
  for (ent = head; ent; ent = ent->next) {
×
682
    if (ent->key == NULL ||
122✔
683
        ent->key->hash != h) {
61✔
684
      continue;
×
685
    }
686

687
    /* Matching hashes.  Now to see if the keys themselves match. */
688
    if (tab->keycmp(ent->key->key_data, ent->key->key_datasz,
61✔
689
        key_data, key_datasz) == 0) {
690
      const void *value_data;
691

692
      value_data = ent->value_data;
61✔
693
      if (value_datasz) {
61✔
694
        *value_datasz = ent->value_datasz;
1✔
695
      }
696

697
      tab_entry_remove(tab, ent);
61✔
698
      tab_entry_free(tab, ent);
61✔
699
      tab->cache_ent = NULL;
61✔
700

701
      return value_data;
61✔
702
    }
703
  }
704

705
  tab->cache_ent = NULL;
×
706

707
  errno = EINVAL;
×
708
  return NULL;
×
709
}
710

711
int pr_table_kset(pr_table_t *tab, const void *key_data, size_t key_datasz,
1,232✔
712
    const void *value_data, size_t value_datasz) {
713
  unsigned int h;
714
  pr_table_entry_t *head, *ent;
715

716
  /* XXX Should callers be allowed to set NULL values for keys? */
717

718
  if (tab == NULL ||
2,464✔
719
      key_data == NULL ||
2,464✔
720
      (value_datasz > 0 && value_data == NULL)) {
1,232✔
721
    errno = EINVAL;
1✔
722
    return -1;
1✔
723
  }
724

725
  if (tab->nents == 0) {
1,231✔
726
    errno = ENOENT;
1✔
727
    return -1;
1✔
728
  }
729

730
  /* Don't forget to add in the random seed data. */
731
  h = tab->keyhash(key_data, key_datasz) + tab->seed;
1,230✔
732

733
  /* Has the caller already looked up this same key previously?
734
   * If so, continue the lookup where we left off.  In this case,
735
   * "same key" means the _exact same pointer_, not identical data.
736
   */
737
  if (tab->val_iter_ent &&
1,230✔
738
      tab->val_iter_ent->key->key_data == key_data) {
×
739
    head = tab->val_iter_ent->next;
×
740

741
  } else if ((tab->flags & PR_TABLE_FL_USE_CACHE) &&
1,230✔
742
             tab->cache_ent &&
×
743
             tab->cache_ent->key->key_data == key_data) {
×
744

745
     /* If the cached lookup entry matches, we'll use it. */
746
     head = tab->cache_ent->next;
×
747

748
  } else {
749
    unsigned int idx = h % tab->nchains;
1,230✔
750
    head = tab->chains[idx];
1,230✔
751
  }
752

753
  if (head == NULL) {
1,230✔
754
    tab->cache_ent = NULL;
×
755
    tab->val_iter_ent = NULL;
×
756

757
    errno = ENOENT;
×
758
    return -1;
×
759
  }
760

761
  for (ent = head; ent; ent = ent->next) {
3✔
762
    if (ent->key == NULL ||
2,466✔
763
        ent->key->hash != h) {
1,233✔
764
      continue;
3✔
765
    }
766

767
    /* Matching hashes.  Now to see if the keys themselves match. */
768
    if (tab->keycmp(ent->key->key_data, ent->key->key_datasz,
1,230✔
769
        key_data, key_datasz) == 0) {
770

771
      if (ent->value_data == value_data) {
1,230✔
772
        errno = EEXIST;
×
773
        return -1;
×
774
      }
775

776
      ent->value_data = value_data;
1,230✔
777
      ent->value_datasz = value_datasz;
1,230✔
778

779
      if (tab->flags & PR_TABLE_FL_USE_CACHE) {
1,230✔
780
        tab->cache_ent = ent;
×
781
      }
782

783
      if (tab->flags & PR_TABLE_FL_MULTI_VALUE) {
1,230✔
784
        tab->val_iter_ent = ent;
×
785
      }
786

787
      return 0;
788
    }
789
  }
790

791
  tab->cache_ent = NULL;
×
792
  tab->val_iter_ent = NULL;
×
793

794
  errno = EINVAL;
×
795
  return -1;
×
796
}
797

798
int pr_table_add(pr_table_t *tab, const char *key_data, const void *value_data,
1,890✔
799
    size_t value_datasz) {
800

801
  if (tab == NULL ||
3,780✔
802
      key_data == NULL) {
1,890✔
803
    errno = EINVAL;
3✔
804
    return -1;
3✔
805
  }
806

807
  if (value_data &&
3,774✔
808
      value_datasz == 0) {
1,887✔
809
    value_datasz = strlen((char *) value_data) + 1;
29✔
810
  }
811

812
  return pr_table_kadd(tab, key_data, strlen(key_data) + 1, value_data,
1,887✔
813
    value_datasz);
814
}
815

816
int pr_table_add_dup(pr_table_t *tab, const char *key_data,
22✔
817
    const void *value_data, size_t value_datasz) {
818
  void *dup_data;
819

820
  if (tab == NULL ||
44✔
821
      key_data == NULL) {
22✔
822
    errno = EINVAL;
2✔
823
    return -1;
2✔
824
  }
825

826
  if (value_data == NULL) {
20✔
827
    if (value_datasz == 0) {
3✔
828
      return 0;
829
    }
830

831
    errno = EINVAL;
1✔
832
    return -1;
1✔
833
  }
834

835
  if (value_data != NULL &&
17✔
836
      value_datasz == 0) {
17✔
837
    value_datasz = strlen((const char *) value_data) + 1;
15✔
838
  }
839

840
  dup_data = pcalloc(tab->pool, value_datasz);
17✔
841
  memcpy(dup_data, value_data, value_datasz);
17✔
842

843
  return pr_table_add(tab, key_data, dup_data, value_datasz);
17✔
844
}
845

846
pr_table_t *pr_table_nalloc(pool *p, int flags, unsigned int nchains) {
2,041✔
847
  pr_table_t *tab;
848
  pool *tab_pool;
849

850
  if (p == NULL ||
4,082✔
851
      nchains == 0) {
2,041✔
852
    errno = EINVAL;
3✔
853
    return NULL;
3✔
854
  }
855

856
  tab_pool = make_sub_pool(p);
2,038✔
857
  pr_pool_tag(tab_pool, "table pool");
2,038✔
858

859
  tab = pcalloc(tab_pool, sizeof(pr_table_t));
2,038✔
860
  tab->pool = tab_pool;
2,038✔
861
  tab->flags = flags;
2,038✔
862
  tab->nchains = nchains;
2,038✔
863
  tab->chains = pcalloc(tab_pool,
2,038✔
864
    sizeof(pr_table_entry_t *) * tab->nchains);
2,038✔
865

866
  tab->keycmp = key_cmp;
2,038✔
867
  tab->keyhash = key_hash;
2,038✔
868
  tab->entinsert = entry_insert;
2,038✔
869
  tab->entremove = entry_remove;
2,038✔
870

871
  tab->seed = tab_get_seed();
2,038✔
872
  tab->nmaxents = PR_TABLE_DEFAULT_MAX_ENTS;
2,038✔
873

874
  return tab;
2,038✔
875
}
876

877
pr_table_t *pr_table_alloc(pool *p, int flags) {
1,642✔
878
  return pr_table_nalloc(p, flags, PR_TABLE_DEFAULT_NCHAINS);
1,642✔
879
}
880

881
int pr_table_count(pr_table_t *tab) {
2,456✔
882
  if (tab == NULL) {
2,456✔
883
    errno = EINVAL;
1,561✔
884
    return -1;
1,561✔
885
  }
886

887
  return tab->nents;
895✔
888
}
889

890
int pr_table_do(pr_table_t *tab, int (*cb)(const void *key_data,
6✔
891
    size_t key_datasz, const void *value_data, size_t value_datasz,
892
    void *user_data), void *user_data, int flags) {
893
  register unsigned int i;
894

895
  if (tab == NULL ||
12✔
896
      cb == NULL) {
6✔
897
    errno = EINVAL;
2✔
898
    return -1;
2✔
899
  }
900

901
  if (tab->nents == 0) {
4✔
902
    return 0;
903
  }
904

905
  for (i = 0; i < tab->nchains; i++) {
521✔
906
    pr_table_entry_t *ent;
907

908
    ent = tab->chains[i];
522✔
909
    while (ent != NULL) {
1,048✔
910
      pr_table_entry_t *next_ent;
911
      int res;
912

913
      next_ent = ent->next;
5✔
914

915
      if (!handling_signal) {
5✔
916
        pr_signals_handle();
5✔
917
      }
918

919
      res = cb(ent->key->key_data, ent->key->key_datasz, ent->value_data,
5✔
920
        ent->value_datasz, user_data);
921
      if (res < 0 &&
8✔
922
          !(flags & PR_TABLE_DO_FL_ALL)) {
3✔
923
        errno = EPERM;
1✔
924
        return -1;
1✔
925
      }
926

927
      ent = next_ent;
928
    }
929
  }
930

931
  return 0;
932
}
933

934
int pr_table_empty(pr_table_t *tab) {
109✔
935
  register unsigned int i;
936

937
  if (tab == NULL) {
109✔
938
    errno = EINVAL;
1✔
939
    return -1;
1✔
940
  }
941

942
  if (tab->nents == 0) {
108✔
943
    return 0;
944
  }
945

946
  for (i = 0; i < tab->nchains; i++) {
5,121✔
947
    pr_table_entry_t *e;
948

949
    e = tab->chains[i];
5,121✔
950
    while (e != NULL) {
10,265✔
951
      if (!handling_signal) {
23✔
952
        pr_signals_handle();
23✔
953
      }
954

955
      tab_entry_remove(tab, e);
23✔
956
      tab_entry_free(tab, e);
23✔
957

958
      e = tab->chains[i];
23✔
959
    }
960

961
    tab->chains[i] = NULL;
5,121✔
962
  }
963

964
  return 0;
965
}
966

967
int pr_table_exists(pr_table_t *tab, const char *key_data) {
2,682✔
968
  if (tab == NULL ||
5,364✔
969
      key_data == NULL) {
2,682✔
970
    errno = EINVAL;
3✔
971
    return -1;
3✔
972
  }
973

974
  return pr_table_kexists(tab, key_data, strlen(key_data) + 1);
2,679✔
975
}
976

977
int pr_table_free(pr_table_t *tab) {
107✔
978

979
  if (tab == NULL) {
107✔
980
    errno = EINVAL;
1✔
981
    return -1;
1✔
982
  }
983

984
  if (tab->nents != 0) {
106✔
985
    errno = EPERM;
1✔
986
    return -1;
1✔
987
  }
988

989
  destroy_pool(tab->pool);
105✔
990
  return 0;
105✔
991
}
992

993
const void *pr_table_get(pr_table_t *tab, const char *key_data,
9,920✔
994
    size_t *value_datasz) {
995
  size_t key_datasz = 0;
9,920✔
996

997
  if (tab == NULL) {
9,920✔
998
    errno = EINVAL;
1,604✔
999
    return NULL;
1,604✔
1000
  }
1001

1002
  if (key_data) {
8,316✔
1003
    key_datasz = strlen(key_data) + 1;
8,312✔
1004
  }
1005

1006
  return pr_table_kget(tab, key_data, key_datasz, value_datasz);
8,316✔
1007
}
1008

1009
const void *pr_table_knext(pr_table_t *tab, size_t *key_datasz) {
24✔
1010
  pr_table_entry_t *ent, *prev;
1011

1012
  if (tab == NULL) {
24✔
1013
    errno = EINVAL;
1✔
1014
    return NULL;
1✔
1015
  }
1016

1017
  prev = tab->tab_iter_ent;
23✔
1018

1019
  ent = tab_entry_next(tab);
23✔
1020
  while (ent != NULL) {
46✔
1021
    if (!handling_signal) {
12✔
1022
      pr_signals_handle();
12✔
1023
    }
1024

1025
    if (prev &&
13✔
1026
        ent->key == prev->key) {
1✔
1027
      ent = tab_entry_next(tab);
×
1028
      continue;
×
1029
    }
1030

1031
    break;
1032
  }
1033

1034
  if (ent == NULL) {
23✔
1035
    errno = EPERM;
11✔
1036
    return NULL;
11✔
1037
  }
1038

1039
  if (key_datasz != NULL) {
12✔
1040
    *key_datasz = ent->key->key_datasz;
3✔
1041
  }
1042

1043
  return ent->key->key_data;
12✔
1044
}
1045

1046
const void *pr_table_next(pr_table_t *tab) {
19✔
1047
  return pr_table_knext(tab, NULL);
19✔
1048
}
1049

1050
const void *pr_table_remove(pr_table_t *tab, const char *key_data,
79✔
1051
    size_t *value_datasz) {
1052

1053
  if (tab == NULL ||
158✔
1054
      key_data == NULL) {
79✔
1055
    errno = EINVAL;
8✔
1056
    return NULL;
8✔
1057
  }
1058

1059
  return pr_table_kremove(tab, key_data, strlen(key_data) + 1, value_datasz);
71✔
1060
}
1061

1062
int pr_table_rewind(pr_table_t *tab) {
26✔
1063
  if (tab == NULL) {
26✔
1064
    errno = EINVAL;
1✔
1065
    return -1;
1✔
1066
  }
1067

1068
  tab->tab_iter_ent = NULL;
25✔
1069
  return 0;
25✔
1070
}
1071

1072
int pr_table_set(pr_table_t *tab, const char *key_data, const void *value_data,
1,234✔
1073
    size_t value_datasz) {
1074

1075
  if (tab == NULL ||
2,468✔
1076
      key_data == NULL) {
1,234✔
1077
    errno = EINVAL;
2✔
1078
    return -1;
2✔
1079
  }
1080

1081
  if (value_data &&
2,464✔
1082
      value_datasz == 0) {
1,232✔
1083
    value_datasz = strlen((const char *) value_data) + 1;
1✔
1084
  }
1085

1086
  return pr_table_kset(tab, key_data, strlen(key_data) + 1, value_data,
1,232✔
1087
    value_datasz);
1088
}
1089

1090
int pr_table_ctl(pr_table_t *tab, int cmd, void *arg) {
83✔
1091

1092
  if (tab == NULL) {
83✔
1093
    errno = EINVAL;
1✔
1094
    return -1;
1✔
1095
  }
1096

1097
  /* Set control operations can only be performed on an empty table. */
1098
  if (tab->nents != 0) {
82✔
1099
    errno = EPERM;
3✔
1100
    return -1;
3✔
1101
  }
1102

1103
  switch (cmd) {
79✔
1104
    case PR_TABLE_CTL_SET_KEY_HASH:
14✔
1105
      tab->keyhash = arg ?
14✔
1106
        (unsigned int (*)(const void *, size_t)) arg :
14✔
1107
        (unsigned int (*)(const void *, size_t)) key_hash;
1108
      return 0;
14✔
1109

1110
    case PR_TABLE_CTL_SET_FLAGS:
2✔
1111
      if (arg == NULL) {
2✔
1112
        errno = EINVAL;
1✔
1113
        return -1;
1✔
1114
      }
1115

1116
      tab->flags = *((unsigned long *) arg);
1✔
1117
      return 0;
1✔
1118

1119
    case PR_TABLE_CTL_SET_KEY_CMP:
13✔
1120
      tab->keycmp = arg ?
13✔
1121
        (int (*)(const void *, size_t, const void *, size_t)) arg :
13✔
1122
        (int (*)(const void *, size_t, const void *, size_t)) key_cmp;
1123
      return 0;
13✔
1124

1125
    case PR_TABLE_CTL_SET_ENT_INSERT:
1✔
1126
      tab->entinsert = arg ?
1✔
1127
        (void (*)(pr_table_entry_t **, pr_table_entry_t *)) arg :
1✔
1128
        (void (*)(pr_table_entry_t **, pr_table_entry_t *)) entry_insert;
1129
      return 0;
1✔
1130

1131
    case PR_TABLE_CTL_SET_ENT_REMOVE:
1✔
1132
      tab->entremove = arg ?
1✔
1133
        (void (*)(pr_table_entry_t **, pr_table_entry_t *)) arg :
1✔
1134
        (void (*)(pr_table_entry_t **, pr_table_entry_t *)) entry_remove;
1135
      return 0;
1✔
1136

1137
    case PR_TABLE_CTL_SET_NCHAINS: {
3✔
1138
      unsigned int new_nchains;
1139

1140
      if (arg == NULL) {
3✔
1141
        errno = EINVAL;
1✔
1142
        return -1;
1✔
1143
      }
1144

1145
      new_nchains = *((unsigned int *) arg);
2✔
1146
      if (new_nchains == 0) {
2✔
1147
        errno = EINVAL;
1✔
1148
        return -1;
1✔
1149
      }
1150

1151
      tab->nchains = new_nchains;
1✔
1152

1153
      /* Note: by not freeing the memory of the previously allocated
1154
       * chains, this constitutes a minor leak of the table's memory pool.
1155
       */
1156
      tab->chains = pcalloc(tab->pool,
1✔
1157
        sizeof(pr_table_entry_t *) * tab->nchains);
1✔
1158
      return 0;
1✔
1159
    }
1160

1161
    case PR_TABLE_CTL_SET_MAX_ENTS: {
44✔
1162
      unsigned int nmaxents;
1163

1164
      nmaxents = *((unsigned int *) arg);
44✔
1165

1166
      if (nmaxents == 0) {
44✔
1167
        errno = EINVAL;
1✔
1168
        return -1;
1✔
1169
      }
1170

1171
      /* If the given maximum is less than the number of entries currently
1172
       * in the table, we have to return an error; we do not want to get
1173
       * into the business of table truncation just yet.
1174
       */
1175
      if (tab->nents > nmaxents) {
1176
        errno = EPERM;
1177
        return -1;
1178
      }
1179

1180
      tab->nmaxents = nmaxents;
43✔
1181
      return 0;
43✔
1182
    }
1183

1184
    default:
1✔
1185
      errno = EINVAL;
1✔
1186
  }
1187

1188
  return -1;
1✔
1189
}
1190

1191
void *pr_table_pcalloc(pr_table_t *tab, size_t sz) {
43✔
1192
  if (tab == NULL ||
86✔
1193
      sz == 0) {
43✔
1194
    errno = EINVAL;
3✔
1195
    return NULL;
3✔
1196
  }
1197

1198
  return pcalloc(tab->pool, sz);
40✔
1199
}
1200

1201
static void table_printf(const char *fmt, ...) {
2✔
1202
  char buf[PR_TUNABLE_BUFFER_SIZE+1];
1203
  va_list msg;
1204

1205
  memset(buf, '\0', sizeof(buf));
2✔
1206
  va_start(msg, fmt);
2✔
1207
  pr_vsnprintf(buf, sizeof(buf)-1, fmt, msg);
2✔
1208
  va_end(msg);
2✔
1209

1210
  buf[sizeof(buf)-1] = '\0';
2✔
1211
  pr_trace_msg(trace_channel, 19, "dump: %s", buf);
2✔
1212
}
2✔
1213

1214
float pr_table_load(pr_table_t *tab) {
2✔
1215
  float load_factor;
1216

1217
  if (tab == NULL) {
2✔
1218
    errno = EINVAL;
1✔
1219
    return -1.0;
1✔
1220
  }
1221

1222
  load_factor = (tab->nents / tab->nchains);
1✔
1223
  return load_factor;
1✔
1224
}
1225

1226
static int tab_copy_cb(const void *key_data, size_t key_datasz,
×
1227
    const void *value_data, size_t value_datasz, void *user_data) {
1228
  int res;
1229
  pr_table_t *dst_tab;
1230

1231
  dst_tab = user_data;
×
1232

1233
  res = pr_table_kexists(dst_tab, key_data, key_datasz);
×
1234
  if (res > 0) {
×
1235
    res = pr_table_kset(dst_tab, key_data, key_datasz, value_data,
×
1236
      value_datasz);
1237

1238
  } else {
1239
    res = pr_table_kadd(dst_tab, key_data, key_datasz, value_data,
×
1240
      value_datasz);
1241
  }
1242

1243
  return res;
×
1244
}
1245

1246
int pr_table_copy(pr_table_t *dst_tab, pr_table_t *src_tab, int flags) {
×
1247
  int res;
1248

1249
  if (dst_tab == NULL ||
×
1250
      src_tab == NULL) {
×
1251
    errno = EINVAL;
×
1252
    return -1;
×
1253
  }
1254

1255
  /* Future flags may support a DEEP_COPY flag, which would use the
1256
   * dst_tab->pool.
1257
   */
1258
  res = pr_table_do(src_tab, tab_copy_cb, dst_tab, PR_TABLE_DO_FL_ALL);
×
1259
  return res;
×
1260
}
1261

1262
void pr_table_dump(void (*dumpf)(const char *fmt, ...), pr_table_t *tab) {
5✔
1263
  register unsigned int i;
1264

1265
  if (tab == NULL) {
5✔
1266
    return;
1267
  }
1268

1269
  if (dumpf == NULL) {
3✔
1270
    dumpf = table_printf;
1✔
1271
  }
1272

1273
  if (tab->flags == 0) {
3✔
1274
    dumpf("%s", "[table flags]: None");
3✔
1275

1276
  } else {
1277
    if ((tab->flags & PR_TABLE_FL_MULTI_VALUE) &&
×
1278
        (tab->flags & PR_TABLE_FL_USE_CACHE)) {
1279
      dumpf("%s", "[table flags]: MultiValue, UseCache");
×
1280

1281
    } else {
1282
      if (tab->flags & PR_TABLE_FL_MULTI_VALUE) {
×
1283
        dumpf("%s", "[table flags]: MultiValue");
×
1284
      }
1285

1286
      if (tab->flags & PR_TABLE_FL_USE_CACHE) {
×
1287
        dumpf("%s", "[table flags]: UseCache");
×
1288
      }
1289
    }
1290
  }
1291

1292
  if (tab->nents == 0) {
3✔
1293
    dumpf("[empty table]");
3✔
1294
    return;
3✔
1295
  }
1296

1297
  dumpf("[table count]: %u", tab->nents);
×
1298
  for (i = 0; i < tab->nchains; i++) {
×
1299
    register unsigned int j = 0;
×
1300
    pr_table_entry_t *ent = tab->chains[i];
×
1301

1302
    while (ent) {
×
1303
      if (!handling_signal) {
×
1304
        pr_signals_handle();
×
1305
      }
1306

1307
      dumpf("[hash %u (%u chains) chain %u#%u] '%s' => '%s' (%u)",
×
1308
        ent->key->hash, tab->nchains, i, j++, ent->key->key_data,
×
1309
        ent->value_data, ent->value_datasz);
1310
      ent = ent->next;
×
1311
    }
1312
  }
1313
}
1314

1315
int table_handling_signal(int do_handle) {
×
1316
  if (do_handle == TRUE ||
×
1317
      do_handle == FALSE) {
1318
    handling_signal = do_handle;
×
1319
    return 0;
×
1320
  }
1321

1322
  errno = EINVAL;
×
1323
  return -1;
×
1324
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc