1 /* $NetBSD: cache.c,v 1.10 2023/01/25 21:43:30 christos Exp $ */
2
3 /*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0
7 *
8 * This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11 *
12 * See the COPYRIGHT file distributed with this work for additional
13 * information regarding copyright ownership.
14 */
15
16 /*! \file */
17
18 #include <inttypes.h>
19 #include <stdbool.h>
20
21 #include <isc/mem.h>
22 #include <isc/print.h>
23 #include <isc/refcount.h>
24 #include <isc/stats.h>
25 #include <isc/string.h>
26 #include <isc/task.h>
27 #include <isc/time.h>
28 #include <isc/timer.h>
29 #include <isc/util.h>
30
31 #include <dns/cache.h>
32 #include <dns/db.h>
33 #include <dns/dbiterator.h>
34 #include <dns/events.h>
35 #include <dns/lib.h>
36 #include <dns/log.h>
37 #include <dns/masterdump.h>
38 #include <dns/rdata.h>
39 #include <dns/rdataset.h>
40 #include <dns/rdatasetiter.h>
41 #include <dns/result.h>
42 #include <dns/stats.h>
43
44 #ifdef HAVE_JSON_C
45 #include <json_object.h>
46 #endif /* HAVE_JSON_C */
47
48 #ifdef HAVE_LIBXML2
49 #include <libxml/xmlwriter.h>
50 #define ISC_XMLCHAR (const xmlChar *)
51 #endif /* HAVE_LIBXML2 */
52
53 #include "rbtdb.h"
54
55 #define CACHE_MAGIC ISC_MAGIC('$', '$', '$', '$')
56 #define VALID_CACHE(cache) ISC_MAGIC_VALID(cache, CACHE_MAGIC)
57
58 /*!
59 * Control incremental cleaning.
60 * DNS_CACHE_MINSIZE is how many bytes is the floor for
61 * dns_cache_setcachesize(). See also DNS_CACHE_CLEANERINCREMENT
62 */
63 #define DNS_CACHE_MINSIZE 2097152U /*%< Bytes. 2097152 = 2 MB */
64 /*!
65 * Control incremental cleaning.
66 * CLEANERINCREMENT is how many nodes are examined in one pass.
67 * See also DNS_CACHE_MINSIZE
68 */
69 #define DNS_CACHE_CLEANERINCREMENT 1000U /*%< Number of nodes. */
70
71 /***
72 *** Types
73 ***/
74
75 /*
76 * A cache_cleaner_t encapsulates the state of the periodic
77 * cache cleaning.
78 */
79
80 typedef struct cache_cleaner cache_cleaner_t;
81
82 typedef enum {
83 cleaner_s_idle, /*%< Waiting for cleaning-interval to expire. */
84 cleaner_s_busy, /*%< Currently cleaning. */
85 cleaner_s_done /*%< Freed enough memory after being overmem. */
86 } cleaner_state_t;
87
88 /*
89 * Convenience macros for comprehensive assertion checking.
90 */
91 #define CLEANER_IDLE(c) \
92 ((c)->state == cleaner_s_idle && (c)->resched_event != NULL)
93 #define CLEANER_BUSY(c) \
94 ((c)->state == cleaner_s_busy && (c)->iterator != NULL && \
95 (c)->resched_event == NULL)
96
97 /*%
98 * Accesses to a cache cleaner object are synchronized through
99 * task/event serialization, or locked from the cache object.
100 */
101 struct cache_cleaner {
102 isc_mutex_t lock;
103 /*%<
104 * Locks overmem_event, overmem. Note: never allocate memory
105 * while holding this lock - that could lead to deadlock since
106 * the lock is take by water() which is called from the memory
107 * allocator.
108 */
109
110 dns_cache_t *cache;
111 isc_task_t *task;
112 isc_event_t *resched_event; /*% Sent by cleaner task to
113 * itself to reschedule */
114 isc_event_t *overmem_event;
115
116 dns_dbiterator_t *iterator;
117 unsigned int increment; /*% Number of names to
118 * clean in one increment */
119 cleaner_state_t state; /*% Idle/Busy. */
120 bool overmem; /*% The cache is in an overmem state.
121 * */
122 bool replaceiterator;
123 };
124
125 /*%
126 * The actual cache object.
127 */
128
129 struct dns_cache {
130 /* Unlocked. */
131 unsigned int magic;
132 isc_mutex_t lock;
133 isc_mutex_t filelock;
134 isc_mem_t *mctx; /* Main cache memory */
135 isc_mem_t *hmctx; /* Heap memory */
136 char *name;
137 isc_refcount_t references;
138 isc_refcount_t live_tasks;
139
140 /* Locked by 'lock'. */
141 dns_rdataclass_t rdclass;
142 dns_db_t *db;
143 cache_cleaner_t cleaner;
144 char *db_type;
145 int db_argc;
146 char **db_argv;
147 size_t size;
148 dns_ttl_t serve_stale_ttl;
149 dns_ttl_t serve_stale_refresh;
150 isc_stats_t *stats;
151
152 /* Locked by 'filelock'. */
153 char *filename;
154 /* Access to the on-disk cache file is also locked by 'filelock'. */
155 };
156
157 /***
158 *** Functions
159 ***/
160
161 static isc_result_t
162 cache_cleaner_init(dns_cache_t *cache, isc_taskmgr_t *taskmgr,
163 isc_timermgr_t *timermgr, cache_cleaner_t *cleaner);
164
165 static void
166 incremental_cleaning_action(isc_task_t *task, isc_event_t *event);
167
168 static void
169 cleaner_shutdown_action(isc_task_t *task, isc_event_t *event);
170
171 static void
172 overmem_cleaning_action(isc_task_t *task, isc_event_t *event);
173
174 static isc_result_t
cache_create_db(dns_cache_t * cache,dns_db_t ** db)175 cache_create_db(dns_cache_t *cache, dns_db_t **db) {
176 isc_result_t result;
177 result = dns_db_create(cache->mctx, cache->db_type, dns_rootname,
178 dns_dbtype_cache, cache->rdclass, cache->db_argc,
179 cache->db_argv, db);
180 if (result == ISC_R_SUCCESS) {
181 dns_db_setservestalettl(*db, cache->serve_stale_ttl);
182 }
183 return (result);
184 }
185
186 isc_result_t
dns_cache_create(isc_mem_t * cmctx,isc_mem_t * hmctx,isc_taskmgr_t * taskmgr,isc_timermgr_t * timermgr,dns_rdataclass_t rdclass,const char * cachename,const char * db_type,unsigned int db_argc,char ** db_argv,dns_cache_t ** cachep)187 dns_cache_create(isc_mem_t *cmctx, isc_mem_t *hmctx, isc_taskmgr_t *taskmgr,
188 isc_timermgr_t *timermgr, dns_rdataclass_t rdclass,
189 const char *cachename, const char *db_type,
190 unsigned int db_argc, char **db_argv, dns_cache_t **cachep) {
191 isc_result_t result;
192 dns_cache_t *cache;
193 int i, extra = 0;
194 isc_task_t *dbtask;
195
196 REQUIRE(cachep != NULL);
197 REQUIRE(*cachep == NULL);
198 REQUIRE(cmctx != NULL);
199 REQUIRE(hmctx != NULL);
200 REQUIRE(cachename != NULL);
201
202 cache = isc_mem_get(cmctx, sizeof(*cache));
203
204 cache->mctx = cache->hmctx = NULL;
205 isc_mem_attach(cmctx, &cache->mctx);
206 isc_mem_attach(hmctx, &cache->hmctx);
207
208 cache->name = NULL;
209 if (cachename != NULL) {
210 cache->name = isc_mem_strdup(cmctx, cachename);
211 }
212
213 isc_mutex_init(&cache->lock);
214 isc_mutex_init(&cache->filelock);
215
216 isc_refcount_init(&cache->references, 1);
217 isc_refcount_init(&cache->live_tasks, 1);
218 cache->rdclass = rdclass;
219 cache->serve_stale_ttl = 0;
220
221 cache->stats = NULL;
222 result = isc_stats_create(cmctx, &cache->stats,
223 dns_cachestatscounter_max);
224 if (result != ISC_R_SUCCESS) {
225 goto cleanup_filelock;
226 }
227
228 cache->db_type = isc_mem_strdup(cmctx, db_type);
229
230 /*
231 * For databases of type "rbt" we pass hmctx to dns_db_create()
232 * via cache->db_argv, followed by the rest of the arguments in
233 * db_argv (of which there really shouldn't be any).
234 */
235 if (strcmp(cache->db_type, "rbt") == 0) {
236 extra = 1;
237 }
238
239 cache->db_argc = db_argc + extra;
240 cache->db_argv = NULL;
241
242 if (cache->db_argc != 0) {
243 cache->db_argv = isc_mem_get(cmctx,
244 cache->db_argc * sizeof(char *));
245
246 for (i = 0; i < cache->db_argc; i++) {
247 cache->db_argv[i] = NULL;
248 }
249
250 cache->db_argv[0] = (char *)hmctx;
251 for (i = extra; i < cache->db_argc; i++) {
252 cache->db_argv[i] = isc_mem_strdup(cmctx,
253 db_argv[i - extra]);
254 }
255 }
256
257 /*
258 * Create the database
259 */
260 cache->db = NULL;
261 result = cache_create_db(cache, &cache->db);
262 if (result != ISC_R_SUCCESS) {
263 goto cleanup_dbargv;
264 }
265 if (taskmgr != NULL) {
266 dbtask = NULL;
267 result = isc_task_create(taskmgr, 1, &dbtask);
268 if (result != ISC_R_SUCCESS) {
269 goto cleanup_db;
270 }
271
272 isc_task_setname(dbtask, "cache_dbtask", NULL);
273 dns_db_settask(cache->db, dbtask);
274 isc_task_detach(&dbtask);
275 }
276
277 cache->filename = NULL;
278
279 cache->magic = CACHE_MAGIC;
280
281 /*
282 * RBT-type cache DB has its own mechanism of cache cleaning and doesn't
283 * need the control of the generic cleaner.
284 */
285 if (strcmp(db_type, "rbt") == 0) {
286 result = cache_cleaner_init(cache, NULL, NULL, &cache->cleaner);
287 } else {
288 result = cache_cleaner_init(cache, taskmgr, timermgr,
289 &cache->cleaner);
290 }
291 if (result != ISC_R_SUCCESS) {
292 goto cleanup_db;
293 }
294
295 result = dns_db_setcachestats(cache->db, cache->stats);
296 if (result != ISC_R_SUCCESS) {
297 goto cleanup_db;
298 }
299
300 *cachep = cache;
301 return (ISC_R_SUCCESS);
302
303 cleanup_db:
304 dns_db_detach(&cache->db);
305 cleanup_dbargv:
306 for (i = extra; i < cache->db_argc; i++) {
307 if (cache->db_argv[i] != NULL) {
308 isc_mem_free(cmctx, cache->db_argv[i]);
309 }
310 }
311 if (cache->db_argv != NULL) {
312 isc_mem_put(cmctx, cache->db_argv,
313 cache->db_argc * sizeof(char *));
314 }
315 isc_mem_free(cmctx, cache->db_type);
316 cleanup_filelock:
317 isc_mutex_destroy(&cache->filelock);
318 isc_stats_detach(&cache->stats);
319 isc_mutex_destroy(&cache->lock);
320 if (cache->name != NULL) {
321 isc_mem_free(cmctx, cache->name);
322 }
323 isc_mem_detach(&cache->hmctx);
324 isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
325 return (result);
326 }
327
328 static void
cache_free(dns_cache_t * cache)329 cache_free(dns_cache_t *cache) {
330 REQUIRE(VALID_CACHE(cache));
331
332 isc_refcount_destroy(&cache->references);
333 isc_refcount_destroy(&cache->live_tasks);
334
335 isc_mem_setwater(cache->mctx, NULL, NULL, 0, 0);
336
337 if (cache->cleaner.task != NULL) {
338 isc_task_detach(&cache->cleaner.task);
339 }
340
341 if (cache->cleaner.overmem_event != NULL) {
342 isc_event_free(&cache->cleaner.overmem_event);
343 }
344
345 if (cache->cleaner.resched_event != NULL) {
346 isc_event_free(&cache->cleaner.resched_event);
347 }
348
349 if (cache->cleaner.iterator != NULL) {
350 dns_dbiterator_destroy(&cache->cleaner.iterator);
351 }
352
353 isc_mutex_destroy(&cache->cleaner.lock);
354
355 if (cache->filename) {
356 isc_mem_free(cache->mctx, cache->filename);
357 cache->filename = NULL;
358 }
359
360 if (cache->db != NULL) {
361 dns_db_detach(&cache->db);
362 }
363
364 if (cache->db_argv != NULL) {
365 /*
366 * We don't free db_argv[0] in "rbt" cache databases
367 * as it's a pointer to hmctx
368 */
369 int extra = 0;
370 if (strcmp(cache->db_type, "rbt") == 0) {
371 extra = 1;
372 }
373 for (int i = extra; i < cache->db_argc; i++) {
374 if (cache->db_argv[i] != NULL) {
375 isc_mem_free(cache->mctx, cache->db_argv[i]);
376 }
377 }
378 isc_mem_put(cache->mctx, cache->db_argv,
379 cache->db_argc * sizeof(char *));
380 }
381
382 if (cache->db_type != NULL) {
383 isc_mem_free(cache->mctx, cache->db_type);
384 }
385
386 if (cache->name != NULL) {
387 isc_mem_free(cache->mctx, cache->name);
388 }
389
390 if (cache->stats != NULL) {
391 isc_stats_detach(&cache->stats);
392 }
393
394 isc_mutex_destroy(&cache->lock);
395 isc_mutex_destroy(&cache->filelock);
396
397 cache->magic = 0;
398 isc_mem_detach(&cache->hmctx);
399 isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
400 }
401
402 void
dns_cache_attach(dns_cache_t * cache,dns_cache_t ** targetp)403 dns_cache_attach(dns_cache_t *cache, dns_cache_t **targetp) {
404 REQUIRE(VALID_CACHE(cache));
405 REQUIRE(targetp != NULL && *targetp == NULL);
406
407 isc_refcount_increment(&cache->references);
408
409 *targetp = cache;
410 }
411
412 void
dns_cache_detach(dns_cache_t ** cachep)413 dns_cache_detach(dns_cache_t **cachep) {
414 dns_cache_t *cache;
415
416 REQUIRE(cachep != NULL);
417 cache = *cachep;
418 *cachep = NULL;
419 REQUIRE(VALID_CACHE(cache));
420
421 if (isc_refcount_decrement(&cache->references) == 1) {
422 cache->cleaner.overmem = false;
423 /*
424 * When the cache is shut down, dump it to a file if one is
425 * specified.
426 */
427 isc_result_t result = dns_cache_dump(cache);
428 if (result != ISC_R_SUCCESS) {
429 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
430 DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
431 "error dumping cache: %s ",
432 isc_result_totext(result));
433 }
434
435 /*
436 * If the cleaner task exists, let it free the cache.
437 */
438 if (isc_refcount_decrement(&cache->live_tasks) > 1) {
439 isc_task_shutdown(cache->cleaner.task);
440 } else {
441 cache_free(cache);
442 }
443 }
444 }
445
446 void
dns_cache_attachdb(dns_cache_t * cache,dns_db_t ** dbp)447 dns_cache_attachdb(dns_cache_t *cache, dns_db_t **dbp) {
448 REQUIRE(VALID_CACHE(cache));
449 REQUIRE(dbp != NULL && *dbp == NULL);
450 REQUIRE(cache->db != NULL);
451
452 LOCK(&cache->lock);
453 dns_db_attach(cache->db, dbp);
454 UNLOCK(&cache->lock);
455 }
456
457 isc_result_t
dns_cache_setfilename(dns_cache_t * cache,const char * filename)458 dns_cache_setfilename(dns_cache_t *cache, const char *filename) {
459 char *newname;
460
461 REQUIRE(VALID_CACHE(cache));
462 REQUIRE(filename != NULL);
463
464 newname = isc_mem_strdup(cache->mctx, filename);
465
466 LOCK(&cache->filelock);
467 if (cache->filename) {
468 isc_mem_free(cache->mctx, cache->filename);
469 }
470 cache->filename = newname;
471 UNLOCK(&cache->filelock);
472
473 return (ISC_R_SUCCESS);
474 }
475
476 isc_result_t
dns_cache_load(dns_cache_t * cache)477 dns_cache_load(dns_cache_t *cache) {
478 isc_result_t result;
479
480 REQUIRE(VALID_CACHE(cache));
481
482 if (cache->filename == NULL) {
483 return (ISC_R_SUCCESS);
484 }
485
486 LOCK(&cache->filelock);
487 result = dns_db_load(cache->db, cache->filename, dns_masterformat_text,
488 0);
489 UNLOCK(&cache->filelock);
490
491 return (result);
492 }
493
494 isc_result_t
dns_cache_dump(dns_cache_t * cache)495 dns_cache_dump(dns_cache_t *cache) {
496 isc_result_t result;
497
498 REQUIRE(VALID_CACHE(cache));
499
500 if (cache->filename == NULL) {
501 return (ISC_R_SUCCESS);
502 }
503
504 LOCK(&cache->filelock);
505 result = dns_master_dump(cache->mctx, cache->db, NULL,
506 &dns_master_style_cache, cache->filename,
507 dns_masterformat_text, NULL);
508 UNLOCK(&cache->filelock);
509 return (result);
510 }
511
512 const char *
dns_cache_getname(dns_cache_t * cache)513 dns_cache_getname(dns_cache_t *cache) {
514 REQUIRE(VALID_CACHE(cache));
515
516 return (cache->name);
517 }
518
519 /*
520 * Initialize the cache cleaner object at *cleaner.
521 * Space for the object must be allocated by the caller.
522 */
523
524 static isc_result_t
cache_cleaner_init(dns_cache_t * cache,isc_taskmgr_t * taskmgr,isc_timermgr_t * timermgr,cache_cleaner_t * cleaner)525 cache_cleaner_init(dns_cache_t *cache, isc_taskmgr_t *taskmgr,
526 isc_timermgr_t *timermgr, cache_cleaner_t *cleaner) {
527 isc_result_t result;
528
529 isc_mutex_init(&cleaner->lock);
530
531 cleaner->increment = DNS_CACHE_CLEANERINCREMENT;
532 cleaner->state = cleaner_s_idle;
533 cleaner->cache = cache;
534 cleaner->iterator = NULL;
535 cleaner->overmem = false;
536 cleaner->replaceiterator = false;
537
538 cleaner->task = NULL;
539 cleaner->resched_event = NULL;
540 cleaner->overmem_event = NULL;
541
542 result = dns_db_createiterator(cleaner->cache->db, false,
543 &cleaner->iterator);
544 if (result != ISC_R_SUCCESS) {
545 goto cleanup;
546 }
547
548 if (taskmgr != NULL && timermgr != NULL) {
549 result = isc_task_create(taskmgr, 1, &cleaner->task);
550 if (result != ISC_R_SUCCESS) {
551 UNEXPECTED_ERROR(__FILE__, __LINE__,
552 "isc_task_create() failed: %s",
553 dns_result_totext(result));
554 result = ISC_R_UNEXPECTED;
555 goto cleanup;
556 }
557 isc_refcount_increment(&cleaner->cache->live_tasks);
558 isc_task_setname(cleaner->task, "cachecleaner", cleaner);
559
560 result = isc_task_onshutdown(cleaner->task,
561 cleaner_shutdown_action, cache);
562 if (result != ISC_R_SUCCESS) {
563 isc_refcount_decrement0(&cleaner->cache->live_tasks);
564 UNEXPECTED_ERROR(__FILE__, __LINE__,
565 "cache cleaner: "
566 "isc_task_onshutdown() failed: %s",
567 dns_result_totext(result));
568 goto cleanup;
569 }
570
571 cleaner->resched_event = isc_event_allocate(
572 cache->mctx, cleaner, DNS_EVENT_CACHECLEAN,
573 incremental_cleaning_action, cleaner,
574 sizeof(isc_event_t));
575
576 cleaner->overmem_event = isc_event_allocate(
577 cache->mctx, cleaner, DNS_EVENT_CACHEOVERMEM,
578 overmem_cleaning_action, cleaner, sizeof(isc_event_t));
579 }
580
581 return (ISC_R_SUCCESS);
582
583 cleanup:
584 if (cleaner->overmem_event != NULL) {
585 isc_event_free(&cleaner->overmem_event);
586 }
587 if (cleaner->resched_event != NULL) {
588 isc_event_free(&cleaner->resched_event);
589 }
590 if (cleaner->task != NULL) {
591 isc_task_detach(&cleaner->task);
592 }
593 if (cleaner->iterator != NULL) {
594 dns_dbiterator_destroy(&cleaner->iterator);
595 }
596 isc_mutex_destroy(&cleaner->lock);
597
598 return (result);
599 }
600
601 static void
begin_cleaning(cache_cleaner_t * cleaner)602 begin_cleaning(cache_cleaner_t *cleaner) {
603 isc_result_t result = ISC_R_SUCCESS;
604
605 REQUIRE(CLEANER_IDLE(cleaner));
606
607 /*
608 * Create an iterator, if it does not already exist, and
609 * position it at the beginning of the cache.
610 */
611 if (cleaner->iterator == NULL) {
612 result = dns_db_createiterator(cleaner->cache->db, false,
613 &cleaner->iterator);
614 }
615 if (result != ISC_R_SUCCESS) {
616 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
617 DNS_LOGMODULE_CACHE, ISC_LOG_WARNING,
618 "cache cleaner could not create "
619 "iterator: %s",
620 isc_result_totext(result));
621 } else {
622 dns_dbiterator_setcleanmode(cleaner->iterator, true);
623 result = dns_dbiterator_first(cleaner->iterator);
624 }
625 if (result != ISC_R_SUCCESS) {
626 /*
627 * If the result is ISC_R_NOMORE, the database is empty,
628 * so there is nothing to be cleaned.
629 */
630 if (result != ISC_R_NOMORE && cleaner->iterator != NULL) {
631 UNEXPECTED_ERROR(__FILE__, __LINE__,
632 "cache cleaner: "
633 "dns_dbiterator_first() failed: %s",
634 dns_result_totext(result));
635 dns_dbiterator_destroy(&cleaner->iterator);
636 } else if (cleaner->iterator != NULL) {
637 result = dns_dbiterator_pause(cleaner->iterator);
638 RUNTIME_CHECK(result == ISC_R_SUCCESS);
639 }
640 } else {
641 /*
642 * Pause the iterator to free its lock.
643 */
644 result = dns_dbiterator_pause(cleaner->iterator);
645 RUNTIME_CHECK(result == ISC_R_SUCCESS);
646
647 isc_log_write(
648 dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
649 ISC_LOG_DEBUG(1), "begin cache cleaning, mem inuse %lu",
650 (unsigned long)isc_mem_inuse(cleaner->cache->mctx));
651 cleaner->state = cleaner_s_busy;
652 isc_task_send(cleaner->task, &cleaner->resched_event);
653 }
654
655 return;
656 }
657
658 static void
end_cleaning(cache_cleaner_t * cleaner,isc_event_t * event)659 end_cleaning(cache_cleaner_t *cleaner, isc_event_t *event) {
660 isc_result_t result;
661
662 REQUIRE(CLEANER_BUSY(cleaner));
663 REQUIRE(event != NULL);
664
665 result = dns_dbiterator_pause(cleaner->iterator);
666 if (result != ISC_R_SUCCESS) {
667 dns_dbiterator_destroy(&cleaner->iterator);
668 }
669
670 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
671 ISC_LOG_DEBUG(1), "end cache cleaning, mem inuse %lu",
672 (unsigned long)isc_mem_inuse(cleaner->cache->mctx));
673
674 cleaner->state = cleaner_s_idle;
675 cleaner->resched_event = event;
676 }
677
678 /*
679 * This is called when the cache either surpasses its upper limit
680 * or shrinks beyond its lower limit.
681 */
682 static void
overmem_cleaning_action(isc_task_t * task,isc_event_t * event)683 overmem_cleaning_action(isc_task_t *task, isc_event_t *event) {
684 cache_cleaner_t *cleaner = event->ev_arg;
685 bool want_cleaning = false;
686
687 UNUSED(task);
688
689 INSIST(task == cleaner->task);
690 INSIST(event->ev_type == DNS_EVENT_CACHEOVERMEM);
691 INSIST(cleaner->overmem_event == NULL);
692
693 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
694 ISC_LOG_DEBUG(1),
695 "overmem_cleaning_action called, "
696 "overmem = %d, state = %d",
697 cleaner->overmem, cleaner->state);
698
699 LOCK(&cleaner->lock);
700
701 if (cleaner->overmem) {
702 if (cleaner->state == cleaner_s_idle) {
703 want_cleaning = true;
704 }
705 } else {
706 if (cleaner->state == cleaner_s_busy) {
707 /*
708 * end_cleaning() can't be called here because
709 * then both cleaner->overmem_event and
710 * cleaner->resched_event will point to this
711 * event. Set the state to done, and then
712 * when the incremental_cleaning_action() event
713 * is posted, it will handle the end_cleaning.
714 */
715 cleaner->state = cleaner_s_done;
716 }
717 }
718
719 cleaner->overmem_event = event;
720
721 UNLOCK(&cleaner->lock);
722
723 if (want_cleaning) {
724 begin_cleaning(cleaner);
725 }
726 }
727
728 /*
729 * Do incremental cleaning.
730 */
731 static void
incremental_cleaning_action(isc_task_t * task,isc_event_t * event)732 incremental_cleaning_action(isc_task_t *task, isc_event_t *event) {
733 cache_cleaner_t *cleaner = event->ev_arg;
734 isc_result_t result;
735 unsigned int n_names;
736 isc_time_t start;
737
738 UNUSED(task);
739
740 INSIST(task == cleaner->task);
741 INSIST(event->ev_type == DNS_EVENT_CACHECLEAN);
742
743 if (cleaner->state == cleaner_s_done) {
744 cleaner->state = cleaner_s_busy;
745 end_cleaning(cleaner, event);
746 LOCK(&cleaner->cache->lock);
747 LOCK(&cleaner->lock);
748 if (cleaner->replaceiterator) {
749 dns_dbiterator_destroy(&cleaner->iterator);
750 (void)dns_db_createiterator(cleaner->cache->db, false,
751 &cleaner->iterator);
752 cleaner->replaceiterator = false;
753 }
754 UNLOCK(&cleaner->lock);
755 UNLOCK(&cleaner->cache->lock);
756 return;
757 }
758
759 INSIST(CLEANER_BUSY(cleaner));
760
761 n_names = cleaner->increment;
762
763 REQUIRE(DNS_DBITERATOR_VALID(cleaner->iterator));
764
765 isc_time_now(&start);
766 while (n_names-- > 0) {
767 dns_dbnode_t *node = NULL;
768
769 result = dns_dbiterator_current(cleaner->iterator, &node, NULL);
770 if (result != ISC_R_SUCCESS) {
771 UNEXPECTED_ERROR(__FILE__, __LINE__,
772 "cache cleaner: "
773 "dns_dbiterator_current() "
774 "failed: %s",
775 dns_result_totext(result));
776
777 end_cleaning(cleaner, event);
778 return;
779 }
780
781 /*
782 * The node was not needed, but was required by
783 * dns_dbiterator_current(). Give up its reference.
784 */
785 dns_db_detachnode(cleaner->cache->db, &node);
786
787 /*
788 * Step to the next node.
789 */
790 result = dns_dbiterator_next(cleaner->iterator);
791
792 if (result != ISC_R_SUCCESS) {
793 /*
794 * Either the end was reached (ISC_R_NOMORE) or
795 * some error was signaled. If the cache is still
796 * overmem and no error was encountered,
797 * keep trying to clean it, otherwise stop cleaning.
798 */
799 if (result != ISC_R_NOMORE) {
800 UNEXPECTED_ERROR(__FILE__, __LINE__,
801 "cache cleaner: "
802 "dns_dbiterator_next() "
803 "failed: %s",
804 dns_result_totext(result));
805 } else if (cleaner->overmem) {
806 result =
807 dns_dbiterator_first(cleaner->iterator);
808 if (result == ISC_R_SUCCESS) {
809 isc_log_write(dns_lctx,
810 DNS_LOGCATEGORY_DATABASE,
811 DNS_LOGMODULE_CACHE,
812 ISC_LOG_DEBUG(1),
813 "cache cleaner: "
814 "still overmem, "
815 "reset and try again");
816 continue;
817 }
818 }
819
820 end_cleaning(cleaner, event);
821 return;
822 }
823 }
824
825 /*
826 * We have successfully performed a cleaning increment but have
827 * not gone through the entire cache. Free the iterator locks
828 * and reschedule another batch. If it fails, just try to continue
829 * anyway.
830 */
831 result = dns_dbiterator_pause(cleaner->iterator);
832 RUNTIME_CHECK(result == ISC_R_SUCCESS);
833
834 isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_CACHE,
835 ISC_LOG_DEBUG(1),
836 "cache cleaner: checked %u nodes, "
837 "mem inuse %lu, sleeping",
838 cleaner->increment,
839 (unsigned long)isc_mem_inuse(cleaner->cache->mctx));
840
841 isc_task_send(task, &event);
842 INSIST(CLEANER_BUSY(cleaner));
843 return;
844 }
845
846 /*
847 * Do immediate cleaning.
848 */
849 isc_result_t
dns_cache_clean(dns_cache_t * cache,isc_stdtime_t now)850 dns_cache_clean(dns_cache_t *cache, isc_stdtime_t now) {
851 isc_result_t result;
852 dns_dbiterator_t *iterator = NULL;
853
854 REQUIRE(VALID_CACHE(cache));
855
856 result = dns_db_createiterator(cache->db, 0, &iterator);
857 if (result != ISC_R_SUCCESS) {
858 return (result);
859 }
860
861 result = dns_dbiterator_first(iterator);
862
863 while (result == ISC_R_SUCCESS) {
864 dns_dbnode_t *node = NULL;
865 result = dns_dbiterator_current(iterator, &node,
866 (dns_name_t *)NULL);
867 if (result != ISC_R_SUCCESS) {
868 break;
869 }
870
871 /*
872 * Check TTLs, mark expired rdatasets stale.
873 */
874 result = dns_db_expirenode(cache->db, node, now);
875 if (result != ISC_R_SUCCESS) {
876 UNEXPECTED_ERROR(__FILE__, __LINE__,
877 "cache cleaner: dns_db_expirenode() "
878 "failed: %s",
879 dns_result_totext(result));
880 /*
881 * Continue anyway.
882 */
883 }
884
885 /*
886 * This is where the actual freeing takes place.
887 */
888 dns_db_detachnode(cache->db, &node);
889
890 result = dns_dbiterator_next(iterator);
891 }
892
893 dns_dbiterator_destroy(&iterator);
894
895 if (result == ISC_R_NOMORE) {
896 result = ISC_R_SUCCESS;
897 }
898
899 return (result);
900 }
901
902 static void
water(void * arg,int mark)903 water(void *arg, int mark) {
904 dns_cache_t *cache = arg;
905 bool overmem = (mark == ISC_MEM_HIWATER);
906
907 REQUIRE(VALID_CACHE(cache));
908
909 LOCK(&cache->cleaner.lock);
910
911 if (overmem != cache->cleaner.overmem) {
912 dns_db_overmem(cache->db, overmem);
913 cache->cleaner.overmem = overmem;
914 isc_mem_waterack(cache->mctx, mark);
915 }
916
917 if (cache->cleaner.overmem_event != NULL) {
918 isc_task_send(cache->cleaner.task,
919 &cache->cleaner.overmem_event);
920 }
921
922 UNLOCK(&cache->cleaner.lock);
923 }
924
925 void
dns_cache_setcachesize(dns_cache_t * cache,size_t size)926 dns_cache_setcachesize(dns_cache_t *cache, size_t size) {
927 size_t hiwater, lowater;
928
929 REQUIRE(VALID_CACHE(cache));
930
931 /*
932 * Impose a minimum cache size; pathological things happen if there
933 * is too little room.
934 */
935 if (size != 0U && size < DNS_CACHE_MINSIZE) {
936 size = DNS_CACHE_MINSIZE;
937 }
938
939 LOCK(&cache->lock);
940 cache->size = size;
941 UNLOCK(&cache->lock);
942
943 hiwater = size - (size >> 3); /* Approximately 7/8ths. */
944 lowater = size - (size >> 2); /* Approximately 3/4ths. */
945
946 /*
947 * If the cache was overmem and cleaning, but now with the new limits
948 * it is no longer in an overmem condition, then the next
949 * isc_mem_put for cache memory will do the right thing and trigger
950 * water().
951 */
952
953 if (size == 0U || hiwater == 0U || lowater == 0U) {
954 /*
955 * Disable cache memory limiting.
956 */
957 isc_mem_setwater(cache->mctx, water, cache, 0, 0);
958 } else {
959 /*
960 * Establish new cache memory limits (either for the first
961 * time, or replacing other limits).
962 */
963 isc_mem_setwater(cache->mctx, water, cache, hiwater, lowater);
964 }
965
966 dns_db_adjusthashsize(cache->db, size);
967 }
968
969 size_t
dns_cache_getcachesize(dns_cache_t * cache)970 dns_cache_getcachesize(dns_cache_t *cache) {
971 size_t size;
972
973 REQUIRE(VALID_CACHE(cache));
974
975 LOCK(&cache->lock);
976 size = cache->size;
977 UNLOCK(&cache->lock);
978
979 return (size);
980 }
981
982 void
dns_cache_setservestalettl(dns_cache_t * cache,dns_ttl_t ttl)983 dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl) {
984 REQUIRE(VALID_CACHE(cache));
985
986 LOCK(&cache->lock);
987 cache->serve_stale_ttl = ttl;
988 UNLOCK(&cache->lock);
989
990 (void)dns_db_setservestalettl(cache->db, ttl);
991 }
992
993 dns_ttl_t
dns_cache_getservestalettl(dns_cache_t * cache)994 dns_cache_getservestalettl(dns_cache_t *cache) {
995 dns_ttl_t ttl;
996 isc_result_t result;
997
998 REQUIRE(VALID_CACHE(cache));
999
1000 /*
1001 * Could get it straight from the dns_cache_t, but use db
1002 * to confirm the value that the db is really using.
1003 */
1004 result = dns_db_getservestalettl(cache->db, &ttl);
1005 return (result == ISC_R_SUCCESS ? ttl : 0);
1006 }
1007
1008 void
dns_cache_setservestalerefresh(dns_cache_t * cache,dns_ttl_t interval)1009 dns_cache_setservestalerefresh(dns_cache_t *cache, dns_ttl_t interval) {
1010 REQUIRE(VALID_CACHE(cache));
1011
1012 LOCK(&cache->lock);
1013 cache->serve_stale_refresh = interval;
1014 UNLOCK(&cache->lock);
1015
1016 (void)dns_db_setservestalerefresh(cache->db, interval);
1017 }
1018
1019 dns_ttl_t
dns_cache_getservestalerefresh(dns_cache_t * cache)1020 dns_cache_getservestalerefresh(dns_cache_t *cache) {
1021 isc_result_t result;
1022 dns_ttl_t interval;
1023
1024 REQUIRE(VALID_CACHE(cache));
1025
1026 result = dns_db_getservestalerefresh(cache->db, &interval);
1027 return (result == ISC_R_SUCCESS ? interval : 0);
1028 }
1029
1030 /*
1031 * The cleaner task is shutting down; do the necessary cleanup.
1032 */
1033 static void
cleaner_shutdown_action(isc_task_t * task,isc_event_t * event)1034 cleaner_shutdown_action(isc_task_t *task, isc_event_t *event) {
1035 dns_cache_t *cache = event->ev_arg;
1036
1037 UNUSED(task);
1038
1039 INSIST(task == cache->cleaner.task);
1040 INSIST(event->ev_type == ISC_TASKEVENT_SHUTDOWN);
1041
1042 if (CLEANER_BUSY(&cache->cleaner)) {
1043 end_cleaning(&cache->cleaner, event);
1044 } else {
1045 isc_event_free(&event);
1046 }
1047
1048 /* Make sure we don't reschedule anymore. */
1049 (void)isc_task_purge(task, NULL, DNS_EVENT_CACHECLEAN, NULL);
1050
1051 isc_refcount_decrementz(&cache->live_tasks);
1052
1053 cache_free(cache);
1054 }
1055
1056 isc_result_t
dns_cache_flush(dns_cache_t * cache)1057 dns_cache_flush(dns_cache_t *cache) {
1058 dns_db_t *db = NULL, *olddb;
1059 dns_dbiterator_t *dbiterator = NULL, *olddbiterator = NULL;
1060 isc_result_t result;
1061
1062 result = cache_create_db(cache, &db);
1063 if (result != ISC_R_SUCCESS) {
1064 return (result);
1065 }
1066
1067 result = dns_db_createiterator(db, false, &dbiterator);
1068 if (result != ISC_R_SUCCESS) {
1069 dns_db_detach(&db);
1070 return (result);
1071 }
1072
1073 LOCK(&cache->lock);
1074 LOCK(&cache->cleaner.lock);
1075 if (cache->cleaner.state == cleaner_s_idle) {
1076 olddbiterator = cache->cleaner.iterator;
1077 cache->cleaner.iterator = dbiterator;
1078 dbiterator = NULL;
1079 } else {
1080 if (cache->cleaner.state == cleaner_s_busy) {
1081 cache->cleaner.state = cleaner_s_done;
1082 }
1083 cache->cleaner.replaceiterator = true;
1084 }
1085 olddb = cache->db;
1086 cache->db = db;
1087 dns_db_setcachestats(cache->db, cache->stats);
1088 UNLOCK(&cache->cleaner.lock);
1089 UNLOCK(&cache->lock);
1090
1091 if (dbiterator != NULL) {
1092 dns_dbiterator_destroy(&dbiterator);
1093 }
1094 if (olddbiterator != NULL) {
1095 dns_dbiterator_destroy(&olddbiterator);
1096 }
1097 dns_db_detach(&olddb);
1098
1099 return (ISC_R_SUCCESS);
1100 }
1101
1102 static isc_result_t
clearnode(dns_db_t * db,dns_dbnode_t * node)1103 clearnode(dns_db_t *db, dns_dbnode_t *node) {
1104 isc_result_t result;
1105 dns_rdatasetiter_t *iter = NULL;
1106
1107 result = dns_db_allrdatasets(db, node, NULL, DNS_DB_STALEOK,
1108 (isc_stdtime_t)0, &iter);
1109 if (result != ISC_R_SUCCESS) {
1110 return (result);
1111 }
1112
1113 for (result = dns_rdatasetiter_first(iter); result == ISC_R_SUCCESS;
1114 result = dns_rdatasetiter_next(iter))
1115 {
1116 dns_rdataset_t rdataset;
1117 dns_rdataset_init(&rdataset);
1118
1119 dns_rdatasetiter_current(iter, &rdataset);
1120 result = dns_db_deleterdataset(db, node, NULL, rdataset.type,
1121 rdataset.covers);
1122 dns_rdataset_disassociate(&rdataset);
1123 if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) {
1124 break;
1125 }
1126 }
1127
1128 if (result == ISC_R_NOMORE) {
1129 result = ISC_R_SUCCESS;
1130 }
1131
1132 dns_rdatasetiter_destroy(&iter);
1133 return (result);
1134 }
1135
1136 static isc_result_t
cleartree(dns_db_t * db,const dns_name_t * name)1137 cleartree(dns_db_t *db, const dns_name_t *name) {
1138 isc_result_t result, answer = ISC_R_SUCCESS;
1139 dns_dbiterator_t *iter = NULL;
1140 dns_dbnode_t *node = NULL, *top = NULL;
1141 dns_fixedname_t fnodename;
1142 dns_name_t *nodename;
1143
1144 /*
1145 * Create the node if it doesn't exist so dns_dbiterator_seek()
1146 * can find it. We will continue even if this fails.
1147 */
1148 (void)dns_db_findnode(db, name, true, &top);
1149
1150 nodename = dns_fixedname_initname(&fnodename);
1151
1152 result = dns_db_createiterator(db, 0, &iter);
1153 if (result != ISC_R_SUCCESS) {
1154 goto cleanup;
1155 }
1156
1157 result = dns_dbiterator_seek(iter, name);
1158 if (result == DNS_R_PARTIALMATCH) {
1159 result = dns_dbiterator_next(iter);
1160 }
1161 if (result != ISC_R_SUCCESS) {
1162 goto cleanup;
1163 }
1164
1165 while (result == ISC_R_SUCCESS) {
1166 result = dns_dbiterator_current(iter, &node, nodename);
1167 if (result == DNS_R_NEWORIGIN) {
1168 result = ISC_R_SUCCESS;
1169 }
1170 if (result != ISC_R_SUCCESS) {
1171 goto cleanup;
1172 }
1173 /*
1174 * Are we done?
1175 */
1176 if (!dns_name_issubdomain(nodename, name)) {
1177 goto cleanup;
1178 }
1179
1180 /*
1181 * If clearnode fails record and move onto the next node.
1182 */
1183 result = clearnode(db, node);
1184 if (result != ISC_R_SUCCESS && answer == ISC_R_SUCCESS) {
1185 answer = result;
1186 }
1187 dns_db_detachnode(db, &node);
1188 result = dns_dbiterator_next(iter);
1189 }
1190
1191 cleanup:
1192 if (result == ISC_R_NOMORE || result == ISC_R_NOTFOUND) {
1193 result = ISC_R_SUCCESS;
1194 }
1195 if (result != ISC_R_SUCCESS && answer == ISC_R_SUCCESS) {
1196 answer = result;
1197 }
1198 if (node != NULL) {
1199 dns_db_detachnode(db, &node);
1200 }
1201 if (iter != NULL) {
1202 dns_dbiterator_destroy(&iter);
1203 }
1204 if (top != NULL) {
1205 dns_db_detachnode(db, &top);
1206 }
1207
1208 return (answer);
1209 }
1210
1211 isc_result_t
dns_cache_flushname(dns_cache_t * cache,const dns_name_t * name)1212 dns_cache_flushname(dns_cache_t *cache, const dns_name_t *name) {
1213 return (dns_cache_flushnode(cache, name, false));
1214 }
1215
1216 isc_result_t
dns_cache_flushnode(dns_cache_t * cache,const dns_name_t * name,bool tree)1217 dns_cache_flushnode(dns_cache_t *cache, const dns_name_t *name, bool tree) {
1218 isc_result_t result;
1219 dns_dbnode_t *node = NULL;
1220 dns_db_t *db = NULL;
1221
1222 if (tree && dns_name_equal(name, dns_rootname)) {
1223 return (dns_cache_flush(cache));
1224 }
1225
1226 LOCK(&cache->lock);
1227 if (cache->db != NULL) {
1228 dns_db_attach(cache->db, &db);
1229 }
1230 UNLOCK(&cache->lock);
1231 if (db == NULL) {
1232 return (ISC_R_SUCCESS);
1233 }
1234
1235 if (tree) {
1236 result = cleartree(cache->db, name);
1237 } else {
1238 result = dns_db_findnode(cache->db, name, false, &node);
1239 if (result == ISC_R_NOTFOUND) {
1240 result = ISC_R_SUCCESS;
1241 goto cleanup_db;
1242 }
1243 if (result != ISC_R_SUCCESS) {
1244 goto cleanup_db;
1245 }
1246 result = clearnode(cache->db, node);
1247 dns_db_detachnode(cache->db, &node);
1248 }
1249
1250 cleanup_db:
1251 dns_db_detach(&db);
1252 return (result);
1253 }
1254
1255 isc_stats_t *
dns_cache_getstats(dns_cache_t * cache)1256 dns_cache_getstats(dns_cache_t *cache) {
1257 REQUIRE(VALID_CACHE(cache));
1258 return (cache->stats);
1259 }
1260
1261 void
dns_cache_updatestats(dns_cache_t * cache,isc_result_t result)1262 dns_cache_updatestats(dns_cache_t *cache, isc_result_t result) {
1263 REQUIRE(VALID_CACHE(cache));
1264 if (cache->stats == NULL) {
1265 return;
1266 }
1267
1268 switch (result) {
1269 case ISC_R_SUCCESS:
1270 case DNS_R_NCACHENXDOMAIN:
1271 case DNS_R_NCACHENXRRSET:
1272 case DNS_R_CNAME:
1273 case DNS_R_DNAME:
1274 case DNS_R_GLUE:
1275 case DNS_R_ZONECUT:
1276 isc_stats_increment(cache->stats,
1277 dns_cachestatscounter_queryhits);
1278 break;
1279 default:
1280 isc_stats_increment(cache->stats,
1281 dns_cachestatscounter_querymisses);
1282 }
1283 }
1284
1285 /*
1286 * XXX: Much of the following code has been copied in from statschannel.c.
1287 * We should refactor this into a generic function in stats.c that can be
1288 * called from both places.
1289 */
1290 typedef struct cache_dumparg {
1291 isc_statsformat_t type;
1292 void *arg; /* type dependent argument */
1293 int ncounters; /* for general statistics */
1294 int *counterindices; /* for general statistics */
1295 uint64_t *countervalues; /* for general statistics */
1296 isc_result_t result;
1297 } cache_dumparg_t;
1298
1299 static void
getcounter(isc_statscounter_t counter,uint64_t val,void * arg)1300 getcounter(isc_statscounter_t counter, uint64_t val, void *arg) {
1301 cache_dumparg_t *dumparg = arg;
1302
1303 REQUIRE(counter < dumparg->ncounters);
1304 dumparg->countervalues[counter] = val;
1305 }
1306
1307 static void
getcounters(isc_stats_t * stats,isc_statsformat_t type,int ncounters,int * indices,uint64_t * values)1308 getcounters(isc_stats_t *stats, isc_statsformat_t type, int ncounters,
1309 int *indices, uint64_t *values) {
1310 cache_dumparg_t dumparg;
1311
1312 memset(values, 0, sizeof(values[0]) * ncounters);
1313
1314 dumparg.type = type;
1315 dumparg.ncounters = ncounters;
1316 dumparg.counterindices = indices;
1317 dumparg.countervalues = values;
1318
1319 isc_stats_dump(stats, getcounter, &dumparg, ISC_STATSDUMP_VERBOSE);
1320 }
1321
1322 void
dns_cache_dumpstats(dns_cache_t * cache,FILE * fp)1323 dns_cache_dumpstats(dns_cache_t *cache, FILE *fp) {
1324 int indices[dns_cachestatscounter_max];
1325 uint64_t values[dns_cachestatscounter_max];
1326
1327 REQUIRE(VALID_CACHE(cache));
1328
1329 getcounters(cache->stats, isc_statsformat_file,
1330 dns_cachestatscounter_max, indices, values);
1331
1332 fprintf(fp, "%20" PRIu64 " %s\n", values[dns_cachestatscounter_hits],
1333 "cache hits");
1334 fprintf(fp, "%20" PRIu64 " %s\n", values[dns_cachestatscounter_misses],
1335 "cache misses");
1336 fprintf(fp, "%20" PRIu64 " %s\n",
1337 values[dns_cachestatscounter_queryhits],
1338 "cache hits (from query)");
1339 fprintf(fp, "%20" PRIu64 " %s\n",
1340 values[dns_cachestatscounter_querymisses],
1341 "cache misses (from query)");
1342 fprintf(fp, "%20" PRIu64 " %s\n",
1343 values[dns_cachestatscounter_deletelru],
1344 "cache records deleted due to memory exhaustion");
1345 fprintf(fp, "%20" PRIu64 " %s\n",
1346 values[dns_cachestatscounter_deletettl],
1347 "cache records deleted due to TTL expiration");
1348 fprintf(fp, "%20u %s\n", dns_db_nodecount(cache->db),
1349 "cache database nodes");
1350 fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)dns_db_hashsize(cache->db),
1351 "cache database hash buckets");
1352
1353 fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_total(cache->mctx),
1354 "cache tree memory total");
1355 fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_inuse(cache->mctx),
1356 "cache tree memory in use");
1357 fprintf(fp, "%20" PRIu64 " %s\n",
1358 (uint64_t)isc_mem_maxinuse(cache->mctx),
1359 "cache tree highest memory in use");
1360
1361 fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_total(cache->hmctx),
1362 "cache heap memory total");
1363 fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_inuse(cache->hmctx),
1364 "cache heap memory in use");
1365 fprintf(fp, "%20" PRIu64 " %s\n",
1366 (uint64_t)isc_mem_maxinuse(cache->hmctx),
1367 "cache heap highest memory in use");
1368 }
1369
1370 #ifdef HAVE_LIBXML2
1371 #define TRY0(a) \
1372 do { \
1373 xmlrc = (a); \
1374 if (xmlrc < 0) \
1375 goto error; \
1376 } while (0)
1377 static int
renderstat(const char * name,uint64_t value,xmlTextWriterPtr writer)1378 renderstat(const char *name, uint64_t value, xmlTextWriterPtr writer) {
1379 int xmlrc;
1380
1381 TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counter"));
1382 TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name",
1383 ISC_XMLCHAR name));
1384 TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", value));
1385 TRY0(xmlTextWriterEndElement(writer)); /* counter */
1386
1387 error:
1388 return (xmlrc);
1389 }
1390
1391 int
dns_cache_renderxml(dns_cache_t * cache,void * writer0)1392 dns_cache_renderxml(dns_cache_t *cache, void *writer0) {
1393 int indices[dns_cachestatscounter_max];
1394 uint64_t values[dns_cachestatscounter_max];
1395 int xmlrc;
1396 xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0;
1397
1398 REQUIRE(VALID_CACHE(cache));
1399
1400 getcounters(cache->stats, isc_statsformat_file,
1401 dns_cachestatscounter_max, indices, values);
1402 TRY0(renderstat("CacheHits", values[dns_cachestatscounter_hits],
1403 writer));
1404 TRY0(renderstat("CacheMisses", values[dns_cachestatscounter_misses],
1405 writer));
1406 TRY0(renderstat("QueryHits", values[dns_cachestatscounter_queryhits],
1407 writer));
1408 TRY0(renderstat("QueryMisses",
1409 values[dns_cachestatscounter_querymisses], writer));
1410 TRY0(renderstat("DeleteLRU", values[dns_cachestatscounter_deletelru],
1411 writer));
1412 TRY0(renderstat("DeleteTTL", values[dns_cachestatscounter_deletettl],
1413 writer));
1414
1415 TRY0(renderstat("CacheNodes", dns_db_nodecount(cache->db), writer));
1416 TRY0(renderstat("CacheBuckets", dns_db_hashsize(cache->db), writer));
1417
1418 TRY0(renderstat("TreeMemTotal", isc_mem_total(cache->mctx), writer));
1419 TRY0(renderstat("TreeMemInUse", isc_mem_inuse(cache->mctx), writer));
1420 TRY0(renderstat("TreeMemMax", isc_mem_maxinuse(cache->mctx), writer));
1421
1422 TRY0(renderstat("HeapMemTotal", isc_mem_total(cache->hmctx), writer));
1423 TRY0(renderstat("HeapMemInUse", isc_mem_inuse(cache->hmctx), writer));
1424 TRY0(renderstat("HeapMemMax", isc_mem_maxinuse(cache->hmctx), writer));
1425 error:
1426 return (xmlrc);
1427 }
1428 #endif /* ifdef HAVE_LIBXML2 */
1429
1430 #ifdef HAVE_JSON_C
1431 #define CHECKMEM(m) \
1432 do { \
1433 if (m == NULL) { \
1434 result = ISC_R_NOMEMORY; \
1435 goto error; \
1436 } \
1437 } while (0)
1438
1439 isc_result_t
dns_cache_renderjson(dns_cache_t * cache,void * cstats0)1440 dns_cache_renderjson(dns_cache_t *cache, void *cstats0) {
1441 isc_result_t result = ISC_R_SUCCESS;
1442 int indices[dns_cachestatscounter_max];
1443 uint64_t values[dns_cachestatscounter_max];
1444 json_object *obj;
1445 json_object *cstats = (json_object *)cstats0;
1446
1447 REQUIRE(VALID_CACHE(cache));
1448
1449 getcounters(cache->stats, isc_statsformat_file,
1450 dns_cachestatscounter_max, indices, values);
1451
1452 obj = json_object_new_int64(values[dns_cachestatscounter_hits]);
1453 CHECKMEM(obj);
1454 json_object_object_add(cstats, "CacheHits", obj);
1455
1456 obj = json_object_new_int64(values[dns_cachestatscounter_misses]);
1457 CHECKMEM(obj);
1458 json_object_object_add(cstats, "CacheMisses", obj);
1459
1460 obj = json_object_new_int64(values[dns_cachestatscounter_queryhits]);
1461 CHECKMEM(obj);
1462 json_object_object_add(cstats, "QueryHits", obj);
1463
1464 obj = json_object_new_int64(values[dns_cachestatscounter_querymisses]);
1465 CHECKMEM(obj);
1466 json_object_object_add(cstats, "QueryMisses", obj);
1467
1468 obj = json_object_new_int64(values[dns_cachestatscounter_deletelru]);
1469 CHECKMEM(obj);
1470 json_object_object_add(cstats, "DeleteLRU", obj);
1471
1472 obj = json_object_new_int64(values[dns_cachestatscounter_deletettl]);
1473 CHECKMEM(obj);
1474 json_object_object_add(cstats, "DeleteTTL", obj);
1475
1476 obj = json_object_new_int64(dns_db_nodecount(cache->db));
1477 CHECKMEM(obj);
1478 json_object_object_add(cstats, "CacheNodes", obj);
1479
1480 obj = json_object_new_int64(dns_db_hashsize(cache->db));
1481 CHECKMEM(obj);
1482 json_object_object_add(cstats, "CacheBuckets", obj);
1483
1484 obj = json_object_new_int64(isc_mem_total(cache->mctx));
1485 CHECKMEM(obj);
1486 json_object_object_add(cstats, "TreeMemTotal", obj);
1487
1488 obj = json_object_new_int64(isc_mem_inuse(cache->mctx));
1489 CHECKMEM(obj);
1490 json_object_object_add(cstats, "TreeMemInUse", obj);
1491
1492 obj = json_object_new_int64(isc_mem_maxinuse(cache->mctx));
1493 CHECKMEM(obj);
1494 json_object_object_add(cstats, "TreeMemMax", obj);
1495
1496 obj = json_object_new_int64(isc_mem_total(cache->hmctx));
1497 CHECKMEM(obj);
1498 json_object_object_add(cstats, "HeapMemTotal", obj);
1499
1500 obj = json_object_new_int64(isc_mem_inuse(cache->hmctx));
1501 CHECKMEM(obj);
1502 json_object_object_add(cstats, "HeapMemInUse", obj);
1503
1504 obj = json_object_new_int64(isc_mem_maxinuse(cache->hmctx));
1505 CHECKMEM(obj);
1506 json_object_object_add(cstats, "HeapMemMax", obj);
1507
1508 result = ISC_R_SUCCESS;
1509 error:
1510 return (result);
1511 }
1512 #endif /* ifdef HAVE_JSON_C */
1513