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

proftpd / proftpd / 26182518137

20 May 2026 06:49PM UTC coverage: 93.024% (+0.4%) from 92.635%
26182518137

push

github

51329 of 55178 relevant lines covered (93.02%)

226.63 hits per line

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

86.33
/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,
102
    size_t keysz2) {
8,122✔
103
  const char *k1, *k2;
104

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

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

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

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

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

127
  return strncmp((const char *) key1, (const char *) key2, keysz1);
128
}
8,121✔
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) {
138
  unsigned int i = 0;
12,249✔
139
  size_t sz = !keysz ? strlen((const char *) key) : keysz;
12,249✔
140

12,249✔
141
  while (sz--) {
142
    const char *k = key;
91,607✔
143
    unsigned int c;
79,358✔
144

79,358✔
145
    c = k[sz];
146

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

152
    i = (i * 33) + c;
153
  }
79,358✔
154

155
  return i;
156
}
12,249✔
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) {
162
  pr_table_entry_t *ei;
116✔
163

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

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

171
  /* Now, ei points to the last entry in the chain. */
172
  ei->next = e;
173
  e->prev = ei;
116✔
174
}
116✔
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) {
178

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

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

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

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

85✔
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;
1,984✔
199

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

38✔
206
    return k;
207
  }
38✔
208

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

1,946✔
212
  return k;
213
}
1,946✔
214

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

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

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

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

232
    i->next = k;
233

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

85✔
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;
1,994✔
244

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

38✔
251
    return e;
252
  }
38✔
253

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

1,956✔
257
  return e;
258
}
1,956✔
259

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

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

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

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

277
    i->next = e;
278

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

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

1,985✔
287
  if (h &&
288
      h != e) {
1,985✔
289

1,985✔
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);
295
    tab->chains[e->idx] = h;
116✔
296
  }
116✔
297

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

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

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

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

10✔
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;
315

10✔
316
      /* Skip to the next populated chain. */
317
      for (i = tab->tab_iter_ent->idx + 1; i < tab->nchains; i++) {
318
        if (tab->chains[i]) {
1,035✔
319
          ent = tab->chains[i];
1,026✔
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++) {
330
      if (tab->chains[i]) {
1,794✔
331
        ent = tab->chains[i];
1,792✔
332
        break;
333
      }
334
    }
335
  }
336

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

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

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

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

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

85✔
357
static unsigned int tab_get_seed(void) {
358
  unsigned int seed = 0;
2,028✔
359
#ifndef PR_USE_OPENSSL
2,028✔
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));
366
#else
2,028✔
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;
388
}
2,028✔
389

390
/* Public Table API
391
 */
392

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

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

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

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

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

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

1,994✔
425
  /* Find the current chain entry at this index. */
426
  e = tab->chains[idx];
427
  if (e != NULL) {
1,994✔
428
    pr_table_entry_t *ei;
1,994✔
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) {
436
      if (ei->key->hash != h) {
3,103✔
437
        continue;
2,987✔
438
      }
2,977✔
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,
445
          key_data, key_datasz) == 0) {
10✔
446

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

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

457
  } else {
458

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

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

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

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

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

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

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

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

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

496
  if (tab->flags & PR_TABLE_FL_USE_CACHE) {
497
    /* Has the caller already wanted to lookup this same key previously?
1,804✔
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;
509

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

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

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

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

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

535
  tab->cache_ent = NULL;
536

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

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

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

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

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

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

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

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

7,307✔
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 &&
576
      tab->val_iter_ent->key->key_data == key_data) {
7,307✔
577
    head = tab->val_iter_ent->next;
×
578

×
579
  } else if ((tab->flags & PR_TABLE_FL_USE_CACHE) &&
580
             tab->cache_ent &&
7,307✔
581
             tab->cache_ent->key->key_data == key_data) {
3✔
582

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

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

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

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

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

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

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

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

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

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

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

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

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

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

643
  if (tab->nents == 0) {
644
    errno = ENOENT;
76✔
645
    return NULL;
14✔
646
  }
14✔
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) &&
653
      tab->cache_ent &&
62✔
654
      tab->cache_ent->key->key_data == key_data) {
1✔
655
    const void *value_data;
1✔
656

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

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

1✔
666
    return value_data;
667
  }
1✔
668

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

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

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

681
  for (ent = head; ent; ent = ent->next) {
682
    if (ent->key == NULL ||
61✔
683
        ent->key->hash != h) {
61✔
684
      continue;
61✔
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,
689
        key_data, key_datasz) == 0) {
61✔
690
      const void *value_data;
691

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

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

61✔
701
      return value_data;
702
    }
61✔
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,
712
    const void *value_data, size_t value_datasz) {
1,223✔
713
  unsigned int h;
714
  pr_table_entry_t *head, *ent;
1,223✔
715

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

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

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

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

1,221✔
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 &&
738
      tab->val_iter_ent->key->key_data == key_data) {
1,221✔
739
    head = tab->val_iter_ent->next;
×
740

×
741
  } else if ((tab->flags & PR_TABLE_FL_USE_CACHE) &&
742
             tab->cache_ent &&
1,221✔
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;
750
    head = tab->chains[idx];
1,221✔
751
  }
1,221✔
752

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

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

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

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

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

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

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

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

787
      return 0;
788
    }
1,221✔
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,
799
    size_t value_datasz) {
1,876✔
800

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2,028✔
874
  return tab;
875
}
2,028✔
876

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

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

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

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

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

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

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

609✔
908
    ent = tab->chains[i];
909
    while (ent != NULL) {
609✔
910
      pr_table_entry_t *next_ent;
613✔
911
      int res;
5✔
912

5✔
913
      next_ent = ent->next;
914

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

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

927
      ent = next_ent;
928
    }
929
  }
930

931
  return 0;
932
}
933

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

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

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

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

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

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

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

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

964
  return 0;
965
}
966

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

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

977
int pr_table_free(pr_table_t *tab) {
978

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

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

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

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

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

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

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

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

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

1017
  prev = tab->tab_iter_ent;
1018

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

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

1031
    break;
1032
  }
1033

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1151
      tab->nchains = new_nchains;
1152

1✔
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,
1157
        sizeof(pr_table_entry_t *) * tab->nchains);
2✔
1158
      return 0;
1✔
1159
    }
1✔
1160

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

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

44✔
1166
      if (nmaxents == 0) {
1167
        errno = EINVAL;
44✔
1168
        return -1;
1✔
1169
      }
1✔
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;
43✔
1177
        return -1;
1178
      }
1179

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

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

1188
  return -1;
1189
}
1✔
1190

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

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

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

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

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

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

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

1222
  load_factor = (tab->nents / tab->nchains);
1223
  return load_factor;
1✔
1224
}
1✔
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) {
1263
  register unsigned int i;
5✔
1264

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

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

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

3✔
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) {
1293
    dumpf("[empty table]");
3✔
1294
    return;
3✔
1295
  }
3✔
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