Coverage Report

Created: 2025-11-16 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/source3/lib/gencache.c
Line
Count
Source
1
/* 
2
   Unix SMB/CIFS implementation.
3
4
   Generic, persistent and shared between processes cache mechanism for use
5
   by various parts of the Samba code
6
7
   Copyright (C) Rafal Szczesniak    2002
8
   Copyright (C) Volker Lendecke     2009
9
10
   This program is free software; you can redistribute it and/or modify
11
   it under the terms of the GNU General Public License as published by
12
   the Free Software Foundation; either version 3 of the License, or
13
   (at your option) any later version.
14
15
   This program is distributed in the hope that it will be useful,
16
   but WITHOUT ANY WARRANTY; without even the implied warranty of
17
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
   GNU General Public License for more details.
19
20
   You should have received a copy of the GNU General Public License
21
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
*/
23
24
#include "includes.h"
25
#include "lib/gencache.h"
26
#include "system/filesys.h"
27
#include "system/glob.h"
28
#include "util_tdb.h"
29
#include "tdb_wrap/tdb_wrap.h"
30
#include "zlib.h"
31
#include "lib/util/strv.h"
32
#include "lib/util/util_paths.h"
33
34
#undef  DBGC_CLASS
35
0
#define DBGC_CLASS DBGC_TDB
36
37
0
#define GENCACHE_USER_PATH "~/.cache/samba/gencache.tdb"
38
39
static struct tdb_wrap *cache;
40
41
/**
42
 * @file gencache.c
43
 * @brief Generic, persistent and shared between processes cache mechanism
44
 *        for use by various parts of the Samba code
45
 *
46
 **/
47
48
static bool gencache_pull_timeout(TDB_DATA key,
49
          TDB_DATA data,
50
          time_t *pres,
51
          DATA_BLOB *payload);
52
53
struct gencache_timeout {
54
  time_t timeout;
55
};
56
57
bool gencache_timeout_expired(const struct gencache_timeout *t)
58
0
{
59
0
  return t->timeout <= time(NULL);
60
0
}
61
62
/**
63
 * Cache initialisation function. Opens cache tdb file or creates
64
 * it if does not exist.
65
 *
66
 * @return true on successful initialisation of the cache or
67
 *         false on failure
68
 **/
69
70
static bool gencache_init(void)
71
0
{
72
0
  char* cache_fname = NULL;
73
0
  int open_flags = O_RDWR|O_CREAT;
74
0
  int tdb_flags = TDB_INCOMPATIBLE_HASH|TDB_NOSYNC|TDB_MUTEX_LOCKING;
75
0
  int hash_size;
76
77
  /* skip file open if it's already opened */
78
0
  if (cache) {
79
0
    return true;
80
0
  }
81
82
0
  hash_size = lp_parm_int(-1, "gencache", "hash_size", 10000);
83
84
0
  cache_fname = lock_path(talloc_tos(), "gencache.tdb");
85
0
  if (cache_fname == NULL) {
86
0
    return false;
87
0
  }
88
89
0
  DEBUG(5, ("Opening cache file at %s\n", cache_fname));
90
91
0
  cache = tdb_wrap_open(NULL, cache_fname, hash_size,
92
0
            tdb_flags,
93
0
            open_flags, 0644);
94
  /*
95
   * Allow client tools to create a gencache in the home directory
96
   * as a normal user.
97
   */
98
0
  if (cache == NULL && errno == EACCES && geteuid() != 0) {
99
0
    char *cache_dname = NULL, *tmp = NULL;
100
0
    bool ok;
101
102
0
    TALLOC_FREE(cache_fname);
103
104
0
    cache_fname = path_expand_tilde(talloc_tos(),
105
0
            GENCACHE_USER_PATH);
106
0
    if (cache_fname == NULL) {
107
0
      DBG_ERR("Failed to expand path: %s\n",
108
0
        GENCACHE_USER_PATH);
109
0
      return false;
110
0
    }
111
112
0
    tmp = talloc_strdup(talloc_tos(), cache_fname);
113
0
    if (tmp == NULL) {
114
0
      DBG_ERR("No memory!\n");
115
0
      TALLOC_FREE(cache_fname);
116
0
      return false;
117
0
    }
118
119
0
    cache_dname = dirname(tmp);
120
0
    if (cache_dname == NULL) {
121
0
      DBG_ERR("Invalid path: %s\n", cache_fname);
122
0
      TALLOC_FREE(tmp);
123
0
      TALLOC_FREE(cache_fname);
124
0
      return false;
125
0
    }
126
127
0
    ok = directory_create_or_exists_recursive(cache_dname, 0700);
128
0
    if (!ok) {
129
0
      DBG_ERR("Failed to create directory: %s - %s\n",
130
0
        cache_dname, strerror(errno));
131
0
      TALLOC_FREE(tmp);
132
0
      TALLOC_FREE(cache_fname);
133
0
      return false;
134
0
    }
135
0
    TALLOC_FREE(tmp);
136
137
0
    cache = tdb_wrap_open(NULL,
138
0
              cache_fname,
139
0
              hash_size,
140
0
              tdb_flags,
141
0
              open_flags,
142
0
              0644);
143
0
    if (cache != NULL) {
144
0
      DBG_INFO("Opening user cache file %s.\n",
145
0
         cache_fname);
146
0
    }
147
0
  }
148
149
0
  if (cache == NULL) {
150
0
    DEBUG(5, ("Opening %s failed: %s\n", cache_fname,
151
0
        strerror(errno)));
152
0
    TALLOC_FREE(cache_fname);
153
0
    return false;
154
0
  }
155
0
  TALLOC_FREE(cache_fname);
156
157
0
  return true;
158
0
}
159
160
/*
161
 * Walk the hash chain for "key", deleting all expired entries for
162
 * that hash chain
163
 */
164
struct gencache_prune_expired_state {
165
  TALLOC_CTX *mem_ctx;
166
  char *keys;
167
};
168
169
static int gencache_prune_expired_fn(struct tdb_context *tdb,
170
             TDB_DATA key,
171
             TDB_DATA data,
172
             void *private_data)
173
0
{
174
0
  struct gencache_prune_expired_state *state = private_data;
175
0
  struct gencache_timeout t;
176
0
  bool ok = false;
177
0
  bool expired = false;
178
179
0
  if ((key.dsize == 0) || (key.dptr[key.dsize-1] != '\0')) {
180
    /* not a valid record, should never happen */
181
0
    return 0;
182
0
  }
183
184
0
  ok = gencache_pull_timeout(key, data, &t.timeout, NULL);
185
0
  if (ok) {
186
0
    expired = gencache_timeout_expired(&t);
187
0
  }
188
189
0
  if (!ok || expired) {
190
0
    int ret;
191
192
0
    ret = strv_add(state->mem_ctx, &state->keys, (char *)key.dptr);
193
0
    if (ret != 0) {
194
      /*
195
       * Exit the loop. It's unlikely that it will
196
       * succeed next time.
197
       */
198
0
      return -1;
199
0
    }
200
0
  }
201
202
0
  return 0;
203
0
}
204
205
static void gencache_prune_expired(struct tdb_context *tdb,
206
           TDB_DATA chain_key)
207
0
{
208
0
  struct gencache_prune_expired_state state = {
209
0
    .mem_ctx = talloc_tos(),
210
0
  };
211
0
  char *keystr = NULL;
212
0
  int ret;
213
214
0
  ret = tdb_traverse_key_chain(
215
0
    tdb, chain_key, gencache_prune_expired_fn, &state);
216
0
  if (ret == -1) {
217
0
    DBG_DEBUG("tdb_traverse_key_chain failed: %s\n",
218
0
        tdb_errorstr(tdb));
219
0
    return;
220
0
  }
221
222
0
  while ((keystr = strv_next(state.keys, keystr)) != NULL) {
223
0
    TDB_DATA key = string_term_tdb_data(keystr);
224
225
    /*
226
     * We expect the hash chain of "chain_key" to be
227
     * locked. So between gencache_prune_expired_fn
228
     * figuring out "keystr" is expired and the
229
     * tdb_delete, nobody can have reset the timeout.
230
     */
231
0
    tdb_delete(tdb, key);
232
0
  }
233
234
0
  TALLOC_FREE(state.keys);
235
0
}
236
237
/**
238
 * Set an entry in the cache file. If there's no such
239
 * one, then add it.
240
 *
241
 * @param keystr string that represents a key of this entry
242
 * @param blob DATA_BLOB value being cached
243
 * @param timeout time when the value is expired
244
 *
245
 * @retval true when entry is successfully stored
246
 * @retval false on failure
247
 **/
248
249
bool gencache_set_data_blob(const char *keystr, DATA_BLOB blob,
250
          time_t timeout)
251
0
{
252
0
  TDB_DATA key;
253
0
  int ret;
254
0
  TDB_DATA dbufs[3];
255
0
  uint32_t crc;
256
257
0
  if ((keystr == NULL) || (blob.data == NULL)) {
258
0
    return false;
259
0
  }
260
261
0
  key = string_term_tdb_data(keystr);
262
263
0
  if (!gencache_init()) {
264
0
    return false;
265
0
  }
266
267
0
  dbufs[0] = (TDB_DATA) { .dptr = (uint8_t *)&timeout,
268
0
        .dsize = sizeof(time_t) };
269
0
  dbufs[1] = (TDB_DATA) { .dptr = blob.data, .dsize = blob.length };
270
271
0
  crc = crc32(0, Z_NULL, 0);
272
0
  crc = crc32(crc, key.dptr, key.dsize);
273
0
  crc = crc32(crc, dbufs[0].dptr, dbufs[0].dsize);
274
0
  crc = crc32(crc, dbufs[1].dptr, dbufs[1].dsize);
275
276
0
  dbufs[2] = (TDB_DATA) { .dptr = (uint8_t *)&crc,
277
0
        .dsize = sizeof(crc) };
278
279
0
  DBG_DEBUG("Adding cache entry with key=[%s] and timeout="
280
0
             "[%s] (%ld seconds %s)\n", keystr,
281
0
       timestring(talloc_tos(), timeout),
282
0
       ((long int)timeout) - time(NULL),
283
0
       timeout > time(NULL) ? "ahead" : "in the past");
284
285
0
  ret = tdb_chainlock(cache->tdb, key);
286
0
  if (ret == -1) {
287
0
    DBG_WARNING("tdb_chainlock for key [%s] failed: %s\n",
288
0
          keystr, tdb_errorstr(cache->tdb));
289
0
    return false;
290
0
  }
291
292
0
  gencache_prune_expired(cache->tdb, key);
293
294
0
  ret = tdb_storev(cache->tdb, key, dbufs, ARRAY_SIZE(dbufs), 0);
295
296
0
  tdb_chainunlock(cache->tdb, key);
297
298
0
  if (ret == 0) {
299
0
    return true;
300
0
  }
301
0
  if (tdb_error(cache->tdb) != TDB_ERR_CORRUPT) {
302
0
    return false;
303
0
  }
304
305
0
  ret = tdb_wipe_all(cache->tdb);
306
0
  SMB_ASSERT(ret == 0);
307
308
0
  return false;
309
0
}
310
311
/**
312
 * Delete one entry from the cache file.
313
 *
314
 * @param keystr string that represents a key of this entry
315
 *
316
 * @retval true upon successful deletion
317
 * @retval false in case of failure
318
 **/
319
320
bool gencache_del(const char *keystr)
321
0
{
322
0
  TDB_DATA key = string_term_tdb_data(keystr);
323
0
  int ret;
324
325
0
  if (keystr == NULL) {
326
0
    return false;
327
0
  }
328
329
0
  if (!gencache_init()) {
330
0
    return false;
331
0
  }
332
333
0
  DEBUG(10, ("Deleting cache entry (key=[%s])\n", keystr));
334
335
0
  ret = tdb_delete(cache->tdb, key);
336
337
0
  if (ret == 0) {
338
0
    return true;
339
0
  }
340
0
  if (tdb_error(cache->tdb) != TDB_ERR_CORRUPT) {
341
0
    return false;
342
0
  }
343
344
0
  ret = tdb_wipe_all(cache->tdb);
345
0
  SMB_ASSERT(ret == 0);
346
347
0
  return true;   /* We've deleted a bit more... */
348
0
}
349
350
static bool gencache_pull_timeout(TDB_DATA key,
351
          TDB_DATA data,
352
          time_t *pres,
353
          DATA_BLOB *payload)
354
0
{
355
0
  size_t crc_ofs;
356
0
  uint32_t crc, stored_crc;
357
358
0
  if ((data.dptr == NULL) ||
359
0
      (data.dsize < (sizeof(time_t) + sizeof(uint32_t)))) {
360
0
    return false;
361
0
  }
362
363
0
  crc_ofs = data.dsize - sizeof(uint32_t);
364
365
0
  crc = crc32(0, Z_NULL, 0);
366
0
  crc = crc32(crc, key.dptr, key.dsize);
367
0
  crc = crc32(crc, data.dptr, crc_ofs);
368
369
0
  memcpy(&stored_crc, data.dptr + crc_ofs, sizeof(uint32_t));
370
371
0
  if (stored_crc != crc) {
372
0
    return false;
373
0
  }
374
375
0
  if (pres != NULL) {
376
0
    memcpy(pres, data.dptr, sizeof(time_t));
377
0
  }
378
0
  if (payload != NULL) {
379
0
    *payload = (DATA_BLOB) {
380
0
      .data = data.dptr+sizeof(time_t),
381
0
      .length = data.dsize-sizeof(time_t)-sizeof(uint32_t),
382
0
    };
383
0
  }
384
0
  return true;
385
0
}
386
387
struct gencache_parse_state {
388
  void (*parser)(const struct gencache_timeout *timeout,
389
           DATA_BLOB blob,
390
           void *private_data);
391
  void *private_data;
392
  bool format_error;
393
};
394
395
static int gencache_parse_fn(TDB_DATA key, TDB_DATA data, void *private_data)
396
0
{
397
0
  struct gencache_parse_state *state = private_data;
398
0
  struct gencache_timeout t;
399
0
  DATA_BLOB payload;
400
0
  bool ret;
401
402
0
  ret = gencache_pull_timeout(key, data, &t.timeout, &payload);
403
0
  if (!ret) {
404
0
    state->format_error = true;
405
0
    return 0;
406
0
  }
407
0
  state->parser(&t, payload, state->private_data);
408
409
0
  return 0;
410
0
}
411
412
bool gencache_parse(const char *keystr,
413
        void (*parser)(const struct gencache_timeout *timeout,
414
           DATA_BLOB blob,
415
           void *private_data),
416
        void *private_data)
417
0
{
418
0
  struct gencache_parse_state state = {
419
0
    .parser = parser, .private_data = private_data
420
0
  };
421
0
  TDB_DATA key = string_term_tdb_data(keystr);
422
0
  int ret;
423
424
0
  if (keystr == NULL) {
425
0
    return false;
426
0
  }
427
0
  if (!gencache_init()) {
428
0
    return false;
429
0
  }
430
431
0
  ret = tdb_parse_record(cache->tdb, key,
432
0
             gencache_parse_fn, &state);
433
0
  if ((ret == -1) && (tdb_error(cache->tdb) == TDB_ERR_CORRUPT)) {
434
0
    goto wipe;
435
0
  }
436
0
  if (ret == -1) {
437
0
    return false;
438
0
  }
439
0
  if (state.format_error) {
440
0
    ret = tdb_delete(cache->tdb, key);
441
0
    if (ret == -1) {
442
0
      goto wipe;
443
0
    }
444
0
    return false;
445
0
  }
446
0
  return true;
447
448
0
wipe:
449
0
  ret = tdb_wipe_all(cache->tdb);
450
0
  SMB_ASSERT(ret == 0);
451
0
  return false;
452
0
}
453
454
struct gencache_get_data_blob_state {
455
  TALLOC_CTX *mem_ctx;
456
  DATA_BLOB *blob;
457
  time_t timeout;
458
  bool result;
459
};
460
461
static void gencache_get_data_blob_parser(const struct gencache_timeout *t,
462
            DATA_BLOB blob,
463
            void *private_data)
464
0
{
465
0
  struct gencache_get_data_blob_state *state =
466
0
    (struct gencache_get_data_blob_state *)private_data;
467
468
0
  if (t->timeout == 0) {
469
0
    state->result = false;
470
0
    return;
471
0
  }
472
0
  state->timeout = t->timeout;
473
474
0
  if (state->blob == NULL) {
475
0
    state->result = true;
476
0
    return;
477
0
  }
478
479
0
  *state->blob = data_blob_talloc(state->mem_ctx, blob.data,
480
0
          blob.length);
481
0
  if (state->blob->data == NULL) {
482
0
    state->result = false;
483
0
    return;
484
0
  }
485
0
  state->result = true;
486
0
}
487
488
/**
489
 * Get existing entry from the cache file.
490
 *
491
 * @param keystr string that represents a key of this entry
492
 * @param blob DATA_BLOB that is filled with entry's blob
493
 * @param timeout pointer to a time_t that is filled with entry's
494
 *        timeout
495
 *
496
 * @retval true when entry is successfully fetched
497
 * @retval false for failure
498
 **/
499
500
bool gencache_get_data_blob(const char *keystr, TALLOC_CTX *mem_ctx,
501
          DATA_BLOB *blob,
502
          time_t *timeout, bool *was_expired)
503
0
{
504
0
  struct gencache_get_data_blob_state state;
505
0
  bool expired = false;
506
507
0
  state.result = false;
508
0
  state.mem_ctx = mem_ctx;
509
0
  state.blob = blob;
510
511
0
  if (!gencache_parse(keystr, gencache_get_data_blob_parser, &state)) {
512
0
    goto fail;
513
0
  }
514
0
  if (!state.result) {
515
0
    goto fail;
516
0
  }
517
0
  if (state.timeout <= time(NULL)) {
518
    /*
519
     * We're expired, delete the entry. We can't use gencache_del
520
     * here, because that uses gencache_get_data_blob for checking
521
     * the existence of a record. We know the thing exists and
522
     * directly store an empty value with 0 timeout.
523
     */
524
0
    gencache_set(keystr, "", 0);
525
0
    expired = true;
526
0
    goto fail;
527
0
  }
528
0
  if (timeout) {
529
0
    *timeout = state.timeout;
530
0
  }
531
532
0
  return true;
533
534
0
fail:
535
0
  if (was_expired != NULL) {
536
0
    *was_expired = expired;
537
0
  }
538
0
  if (state.result && state.blob) {
539
0
    data_blob_free(state.blob);
540
0
  }
541
0
  return false;
542
0
} 
543
544
/**
545
 * Get existing entry from the cache file.
546
 *
547
 * @param keystr string that represents a key of this entry
548
 * @param valstr buffer that is allocated and filled with the entry value
549
 *        buffer's disposing must be done outside
550
 * @param timeout pointer to a time_t that is filled with entry's
551
 *        timeout
552
 *
553
 * @retval true when entry is successfully fetched
554
 * @retval false for failure
555
 **/
556
557
bool gencache_get(const char *keystr, TALLOC_CTX *mem_ctx, char **value,
558
      time_t *ptimeout)
559
0
{
560
0
  DATA_BLOB blob;
561
0
  bool ret = false;
562
563
0
  ret = gencache_get_data_blob(keystr, mem_ctx, &blob, ptimeout, NULL);
564
0
  if (!ret) {
565
0
    return false;
566
0
  }
567
0
  if ((blob.data == NULL) || (blob.length == 0)) {
568
0
    data_blob_free(&blob);
569
0
    return false;
570
0
  }
571
0
  if (blob.data[blob.length-1] != '\0') {
572
    /* Not NULL terminated, can't be a string */
573
0
    data_blob_free(&blob);
574
0
    return false;
575
0
  }
576
0
  if (value) {
577
    /*
578
     * talloc_move generates a type-punned warning here. As we
579
     * leave the function immediately, do a simple talloc_steal.
580
     */
581
0
    *value = (char *)talloc_steal(mem_ctx, blob.data);
582
0
    return true;
583
0
  }
584
0
  data_blob_free(&blob);
585
0
  return true;
586
0
}
587
588
/**
589
 * Set an entry in the cache file. If there's no such
590
 * one, then add it.
591
 *
592
 * @param keystr string that represents a key of this entry
593
 * @param value text representation value being cached
594
 * @param timeout time when the value is expired
595
 *
596
 * @retval true when entry is successfully stored
597
 * @retval false on failure
598
 **/
599
600
bool gencache_set(const char *keystr, const char *value, time_t timeout)
601
0
{
602
0
  DATA_BLOB blob = data_blob_const(value, strlen(value)+1);
603
0
  return gencache_set_data_blob(keystr, blob, timeout);
604
0
}
605
606
struct gencache_iterate_blobs_state {
607
  void (*fn)(const char *key, DATA_BLOB value,
608
       time_t timeout, void *private_data);
609
  const char *pattern;
610
  void *private_data;
611
};
612
613
static int gencache_iterate_blobs_fn(struct tdb_context *tdb, TDB_DATA key,
614
             TDB_DATA data, void *priv)
615
0
{
616
0
  struct gencache_iterate_blobs_state *state =
617
0
    (struct gencache_iterate_blobs_state *)priv;
618
0
  char *keystr;
619
0
  char *free_key = NULL;
620
0
  time_t timeout;
621
0
  DATA_BLOB payload;
622
623
0
  if (key.dptr[key.dsize-1] == '\0') {
624
0
    keystr = (char *)key.dptr;
625
0
  } else {
626
    /* ensure 0-termination */
627
0
    keystr = talloc_strndup(talloc_tos(), (char *)key.dptr, key.dsize);
628
0
    free_key = keystr;
629
0
    if (keystr == NULL) {
630
0
      goto done;
631
0
    }
632
0
  }
633
634
0
  if (!gencache_pull_timeout(key, data, &timeout, &payload)) {
635
0
    goto done;
636
0
  }
637
638
0
  if (timeout == 0) {
639
    /* delete marker */
640
0
    goto done;
641
0
  }
642
643
0
  if (fnmatch(state->pattern, keystr, 0) != 0) {
644
0
    goto done;
645
0
  }
646
647
0
  DEBUG(10, ("Calling function with arguments "
648
0
       "(key=[%s], timeout=[%s])\n",
649
0
       keystr, timestring(talloc_tos(), timeout)));
650
651
0
  state->fn(keystr, payload, timeout, state->private_data);
652
653
0
 done:
654
0
  TALLOC_FREE(free_key);
655
0
  return 0;
656
0
}
657
658
void gencache_iterate_blobs(void (*fn)(const char *key, DATA_BLOB value,
659
               time_t timeout, void *private_data),
660
          void *private_data, const char *pattern)
661
0
{
662
0
  struct gencache_iterate_blobs_state state;
663
0
  int ret;
664
665
0
  if ((fn == NULL) || (pattern == NULL) || !gencache_init()) {
666
0
    return;
667
0
  }
668
669
0
  DEBUG(5, ("Searching cache keys with pattern %s\n", pattern));
670
671
0
  state.fn = fn;
672
0
  state.pattern = pattern;
673
0
  state.private_data = private_data;
674
675
0
  ret = tdb_traverse(cache->tdb, gencache_iterate_blobs_fn, &state);
676
677
0
  if ((ret == -1) && (tdb_error(cache->tdb) == TDB_ERR_CORRUPT)) {
678
0
    ret = tdb_wipe_all(cache->tdb);
679
0
    SMB_ASSERT(ret == 0);
680
0
  }
681
0
}
682
683
/**
684
 * Iterate through all entries which key matches to specified pattern
685
 *
686
 * @param fn pointer to the function that will be supplied with each single
687
 *        matching cache entry (key, value and timeout) as an arguments
688
 * @param data void pointer to an arbitrary data that is passed directly to the fn
689
 *        function on each call
690
 * @param keystr_pattern pattern the existing entries' keys are matched to
691
 *
692
 **/
693
694
struct gencache_iterate_state {
695
  void (*fn)(const char *key, const char *value, time_t timeout,
696
       void *priv);
697
  void *private_data;
698
};
699
700
static void gencache_iterate_fn(const char *key, DATA_BLOB value,
701
        time_t timeout, void *private_data)
702
0
{
703
0
  struct gencache_iterate_state *state =
704
0
    (struct gencache_iterate_state *)private_data;
705
0
  char *valstr;
706
0
  char *free_val = NULL;
707
708
0
  if (value.data[value.length-1] == '\0') {
709
0
    valstr = (char *)value.data;
710
0
  } else {
711
    /* ensure 0-termination */
712
0
    valstr = talloc_strndup(talloc_tos(), (char *)value.data, value.length);
713
0
    free_val = valstr;
714
0
    if (valstr == NULL) {
715
0
      goto done;
716
0
    }
717
0
  }
718
719
0
  DEBUG(10, ("Calling function with arguments "
720
0
       "(key=[%s], value=[%s], timeout=[%s])\n",
721
0
       key, valstr, timestring(talloc_tos(), timeout)));
722
723
0
  state->fn(key, valstr, timeout, state->private_data);
724
725
0
  done:
726
727
0
  TALLOC_FREE(free_val);
728
0
}
729
730
void gencache_iterate(void (*fn)(const char *key, const char *value,
731
         time_t timeout, void *dptr),
732
                      void *private_data, const char *pattern)
733
0
{
734
0
  struct gencache_iterate_state state;
735
736
0
  if (fn == NULL) {
737
0
    return;
738
0
  }
739
0
  state.fn = fn;
740
0
  state.private_data = private_data;
741
0
  gencache_iterate_blobs(gencache_iterate_fn, &state, pattern);
742
0
}