xref: /netbsd-src/external/mpl/bind/dist/lib/dns/cache.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
1 /*	$NetBSD: cache.c,v 1.13 2025/01/26 16:25:22 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/loop.h>
22 #include <isc/mem.h>
23 #include <isc/refcount.h>
24 #include <isc/result.h>
25 #include <isc/stats.h>
26 #include <isc/string.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/log.h>
35 #include <dns/masterdump.h>
36 #include <dns/rdata.h>
37 #include <dns/rdataset.h>
38 #include <dns/rdatasetiter.h>
39 #include <dns/stats.h>
40 
41 #ifdef HAVE_JSON_C
42 #include <json_object.h>
43 #endif /* HAVE_JSON_C */
44 
45 #ifdef HAVE_LIBXML2
46 #include <libxml/xmlwriter.h>
47 #define ISC_XMLCHAR (const xmlChar *)
48 #endif /* HAVE_LIBXML2 */
49 
50 #define CACHE_MAGIC	   ISC_MAGIC('$', '$', '$', '$')
51 #define VALID_CACHE(cache) ISC_MAGIC_VALID(cache, CACHE_MAGIC)
52 
53 /*
54  * DNS_CACHE_MINSIZE is how many bytes is the floor for
55  * dns_cache_setcachesize().
56  */
57 #define DNS_CACHE_MINSIZE 2097152U /*%< Bytes.  2097152 = 2 MB */
58 
59 /***
60  ***	Types
61  ***/
62 
63 /*%
64  * The actual cache object.
65  */
66 
67 struct dns_cache {
68 	/* Unlocked. */
69 	unsigned int magic;
70 	isc_mutex_t lock;
71 	isc_mem_t *mctx;  /* Memory context for the dns_cache object */
72 	isc_mem_t *hmctx; /* Heap memory */
73 	isc_mem_t *tmctx; /* Tree memory */
74 	isc_loopmgr_t *loopmgr;
75 	char *name;
76 	isc_refcount_t references;
77 
78 	/* Locked by 'lock'. */
79 	dns_rdataclass_t rdclass;
80 	dns_db_t *db;
81 	size_t size;
82 	dns_ttl_t serve_stale_ttl;
83 	dns_ttl_t serve_stale_refresh;
84 	isc_stats_t *stats;
85 	uint32_t maxrrperset;
86 	uint32_t maxtypepername;
87 };
88 
89 /***
90  ***	Functions
91  ***/
92 
93 static isc_result_t
94 cache_create_db(dns_cache_t *cache, dns_db_t **dbp, isc_mem_t **tmctxp,
95 		isc_mem_t **hmctxp) {
96 	isc_result_t result;
97 	char *argv[1] = { 0 };
98 	dns_db_t *db = NULL;
99 	isc_mem_t *tmctx = NULL, *hmctx = NULL;
100 
101 	/*
102 	 * This will be the cache memory context, which is subject
103 	 * to cleaning when the configured memory limits are exceeded.
104 	 */
105 	isc_mem_create(&tmctx);
106 	isc_mem_setname(tmctx, "cache");
107 
108 	/*
109 	 * This will be passed to RBTDB to use for heaps. This is separate
110 	 * from the main cache memory because it can grow quite large under
111 	 * heavy load and could otherwise cause the cache to be cleaned too
112 	 * aggressively.
113 	 */
114 	isc_mem_create(&hmctx);
115 	isc_mem_setname(hmctx, "cache_heap");
116 
117 	/*
118 	 * For databases of type "qpcache" or "rbt" (which are the
119 	 * only cache implementations currently in existence) we pass
120 	 * hmctx to dns_db_create() via argv[0].
121 	 */
122 	argv[0] = (char *)hmctx;
123 	result = dns_db_create(tmctx, CACHEDB_DEFAULT, dns_rootname,
124 			       dns_dbtype_cache, cache->rdclass, 1, argv, &db);
125 	if (result != ISC_R_SUCCESS) {
126 		goto cleanup_mctx;
127 	}
128 	result = dns_db_setcachestats(db, cache->stats);
129 	if (result != ISC_R_SUCCESS) {
130 		goto cleanup_db;
131 	}
132 
133 	dns_db_setservestalettl(db, cache->serve_stale_ttl);
134 	dns_db_setservestalerefresh(db, cache->serve_stale_refresh);
135 	dns_db_setmaxrrperset(db, cache->maxrrperset);
136 	dns_db_setmaxtypepername(db, cache->maxtypepername);
137 
138 	/*
139 	 * XXX this is only used by the RBT cache, and can
140 	 * be removed when it is.
141 	 */
142 	dns_db_setloop(db, isc_loop_main(cache->loopmgr));
143 
144 	*dbp = db;
145 	*hmctxp = hmctx;
146 	*tmctxp = tmctx;
147 
148 	return ISC_R_SUCCESS;
149 
150 cleanup_db:
151 	dns_db_detach(&db);
152 cleanup_mctx:
153 	isc_mem_detach(&hmctx);
154 	isc_mem_detach(&tmctx);
155 
156 	return result;
157 }
158 
159 static void
160 cache_destroy(dns_cache_t *cache) {
161 	isc_stats_detach(&cache->stats);
162 	isc_mutex_destroy(&cache->lock);
163 	isc_mem_free(cache->mctx, cache->name);
164 	if (cache->hmctx != NULL) {
165 		isc_mem_detach(&cache->hmctx);
166 	}
167 	if (cache->tmctx != NULL) {
168 		isc_mem_detach(&cache->tmctx);
169 	}
170 	isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
171 }
172 
173 isc_result_t
174 dns_cache_create(isc_loopmgr_t *loopmgr, dns_rdataclass_t rdclass,
175 		 const char *cachename, isc_mem_t *mctx, dns_cache_t **cachep) {
176 	isc_result_t result;
177 	dns_cache_t *cache = NULL;
178 
179 	REQUIRE(loopmgr != NULL);
180 	REQUIRE(cachename != NULL);
181 	REQUIRE(cachep != NULL && *cachep == NULL);
182 
183 	cache = isc_mem_get(mctx, sizeof(*cache));
184 	*cache = (dns_cache_t){
185 		.rdclass = rdclass,
186 		.name = isc_mem_strdup(mctx, cachename),
187 		.loopmgr = loopmgr,
188 		.references = ISC_REFCOUNT_INITIALIZER(1),
189 		.magic = CACHE_MAGIC,
190 	};
191 
192 	isc_mutex_init(&cache->lock);
193 	isc_mem_attach(mctx, &cache->mctx);
194 
195 	isc_stats_create(mctx, &cache->stats, dns_cachestatscounter_max);
196 
197 	/*
198 	 * Create the database
199 	 */
200 	result = cache_create_db(cache, &cache->db, &cache->tmctx,
201 				 &cache->hmctx);
202 	if (result != ISC_R_SUCCESS) {
203 		goto cleanup;
204 	}
205 
206 	*cachep = cache;
207 	return ISC_R_SUCCESS;
208 
209 cleanup:
210 	cache_destroy(cache);
211 	return result;
212 }
213 
214 static void
215 cache_cleanup(dns_cache_t *cache) {
216 	REQUIRE(VALID_CACHE(cache));
217 
218 	isc_refcount_destroy(&cache->references);
219 	cache->magic = 0;
220 
221 	isc_mem_clearwater(cache->tmctx);
222 	dns_db_detach(&cache->db);
223 
224 	cache_destroy(cache);
225 }
226 
227 #if DNS_CACHE_TRACE
228 ISC_REFCOUNT_TRACE_IMPL(dns_cache, cache_cleanup);
229 #else
230 ISC_REFCOUNT_IMPL(dns_cache, cache_cleanup);
231 #endif
232 
233 void
234 dns_cache_attachdb(dns_cache_t *cache, dns_db_t **dbp) {
235 	REQUIRE(VALID_CACHE(cache));
236 	REQUIRE(dbp != NULL && *dbp == NULL);
237 	REQUIRE(cache->db != NULL);
238 
239 	LOCK(&cache->lock);
240 	dns_db_attach(cache->db, dbp);
241 	UNLOCK(&cache->lock);
242 }
243 
244 const char *
245 dns_cache_getname(dns_cache_t *cache) {
246 	REQUIRE(VALID_CACHE(cache));
247 
248 	return cache->name;
249 }
250 
251 static void
252 updatewater(dns_cache_t *cache) {
253 	size_t hi = cache->size - (cache->size >> 3); /* ~ 7/8ths. */
254 	size_t lo = cache->size - (cache->size >> 2); /* ~ 3/4ths. */
255 	if (cache->size == 0U || hi == 0U || lo == 0U) {
256 		isc_mem_clearwater(cache->tmctx);
257 	} else {
258 		isc_mem_setwater(cache->tmctx, hi, lo);
259 	}
260 }
261 
262 void
263 dns_cache_setcachesize(dns_cache_t *cache, size_t size) {
264 	REQUIRE(VALID_CACHE(cache));
265 
266 	/*
267 	 * Impose a minimum cache size; pathological things happen if there
268 	 * is too little room.
269 	 */
270 	if (size != 0U && size < DNS_CACHE_MINSIZE) {
271 		size = DNS_CACHE_MINSIZE;
272 	}
273 
274 	LOCK(&cache->lock);
275 	cache->size = size;
276 	updatewater(cache);
277 	UNLOCK(&cache->lock);
278 }
279 
280 size_t
281 dns_cache_getcachesize(dns_cache_t *cache) {
282 	size_t size;
283 
284 	REQUIRE(VALID_CACHE(cache));
285 
286 	LOCK(&cache->lock);
287 	size = cache->size;
288 	UNLOCK(&cache->lock);
289 
290 	return size;
291 }
292 
293 void
294 dns_cache_setservestalettl(dns_cache_t *cache, dns_ttl_t ttl) {
295 	REQUIRE(VALID_CACHE(cache));
296 
297 	LOCK(&cache->lock);
298 	cache->serve_stale_ttl = ttl;
299 	UNLOCK(&cache->lock);
300 
301 	(void)dns_db_setservestalettl(cache->db, ttl);
302 }
303 
304 dns_ttl_t
305 dns_cache_getservestalettl(dns_cache_t *cache) {
306 	dns_ttl_t ttl;
307 	isc_result_t result;
308 
309 	REQUIRE(VALID_CACHE(cache));
310 
311 	/*
312 	 * Could get it straight from the dns_cache_t, but use db
313 	 * to confirm the value that the db is really using.
314 	 */
315 	result = dns_db_getservestalettl(cache->db, &ttl);
316 	return result == ISC_R_SUCCESS ? ttl : 0;
317 }
318 
319 void
320 dns_cache_setservestalerefresh(dns_cache_t *cache, dns_ttl_t interval) {
321 	REQUIRE(VALID_CACHE(cache));
322 
323 	LOCK(&cache->lock);
324 	cache->serve_stale_refresh = interval;
325 	UNLOCK(&cache->lock);
326 
327 	(void)dns_db_setservestalerefresh(cache->db, interval);
328 }
329 
330 dns_ttl_t
331 dns_cache_getservestalerefresh(dns_cache_t *cache) {
332 	isc_result_t result;
333 	dns_ttl_t interval;
334 
335 	REQUIRE(VALID_CACHE(cache));
336 
337 	result = dns_db_getservestalerefresh(cache->db, &interval);
338 	return result == ISC_R_SUCCESS ? interval : 0;
339 }
340 
341 isc_result_t
342 dns_cache_flush(dns_cache_t *cache) {
343 	dns_db_t *db = NULL, *olddb;
344 	isc_mem_t *tmctx = NULL, *oldtmctx;
345 	isc_mem_t *hmctx = NULL, *oldhmctx;
346 	isc_result_t result;
347 
348 	result = cache_create_db(cache, &db, &tmctx, &hmctx);
349 	if (result != ISC_R_SUCCESS) {
350 		return result;
351 	}
352 
353 	LOCK(&cache->lock);
354 	isc_mem_clearwater(cache->tmctx);
355 	oldhmctx = cache->hmctx;
356 	cache->hmctx = hmctx;
357 	oldtmctx = cache->tmctx;
358 	cache->tmctx = tmctx;
359 	updatewater(cache);
360 	olddb = cache->db;
361 	cache->db = db;
362 	UNLOCK(&cache->lock);
363 
364 	dns_db_detach(&olddb);
365 	isc_mem_detach(&oldhmctx);
366 	isc_mem_detach(&oldtmctx);
367 
368 	return ISC_R_SUCCESS;
369 }
370 
371 static isc_result_t
372 clearnode(dns_db_t *db, dns_dbnode_t *node) {
373 	isc_result_t result;
374 	dns_rdatasetiter_t *iter = NULL;
375 
376 	result = dns_db_allrdatasets(db, node, NULL, DNS_DB_STALEOK,
377 				     (isc_stdtime_t)0, &iter);
378 	if (result != ISC_R_SUCCESS) {
379 		return result;
380 	}
381 
382 	for (result = dns_rdatasetiter_first(iter); result == ISC_R_SUCCESS;
383 	     result = dns_rdatasetiter_next(iter))
384 	{
385 		dns_rdataset_t rdataset;
386 		dns_rdataset_init(&rdataset);
387 
388 		dns_rdatasetiter_current(iter, &rdataset);
389 		result = dns_db_deleterdataset(db, node, NULL, rdataset.type,
390 					       rdataset.covers);
391 		dns_rdataset_disassociate(&rdataset);
392 		if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) {
393 			break;
394 		}
395 	}
396 
397 	if (result == ISC_R_NOMORE) {
398 		result = ISC_R_SUCCESS;
399 	}
400 
401 	dns_rdatasetiter_destroy(&iter);
402 	return result;
403 }
404 
405 static isc_result_t
406 cleartree(dns_db_t *db, const dns_name_t *name) {
407 	isc_result_t result, answer = ISC_R_SUCCESS;
408 	dns_dbiterator_t *iter = NULL;
409 	dns_dbnode_t *node = NULL, *top = NULL;
410 	dns_fixedname_t fnodename;
411 	dns_name_t *nodename;
412 
413 	/*
414 	 * Create the node if it doesn't exist so dns_dbiterator_seek()
415 	 * can find it.  We will continue even if this fails.
416 	 */
417 	(void)dns_db_findnode(db, name, true, &top);
418 
419 	nodename = dns_fixedname_initname(&fnodename);
420 
421 	result = dns_db_createiterator(db, 0, &iter);
422 	if (result != ISC_R_SUCCESS) {
423 		goto cleanup;
424 	}
425 
426 	result = dns_dbiterator_seek(iter, name);
427 	if (result == DNS_R_PARTIALMATCH) {
428 		result = dns_dbiterator_next(iter);
429 	}
430 	if (result != ISC_R_SUCCESS) {
431 		goto cleanup;
432 	}
433 
434 	while (result == ISC_R_SUCCESS) {
435 		result = dns_dbiterator_current(iter, &node, nodename);
436 		if (result == DNS_R_NEWORIGIN) {
437 			result = ISC_R_SUCCESS;
438 		}
439 		if (result != ISC_R_SUCCESS) {
440 			goto cleanup;
441 		}
442 		/*
443 		 * Are we done?
444 		 */
445 		if (!dns_name_issubdomain(nodename, name)) {
446 			goto cleanup;
447 		}
448 
449 		/*
450 		 * If clearnode fails record and move onto the next node.
451 		 */
452 		result = clearnode(db, node);
453 		if (result != ISC_R_SUCCESS && answer == ISC_R_SUCCESS) {
454 			answer = result;
455 		}
456 		dns_db_detachnode(db, &node);
457 		result = dns_dbiterator_next(iter);
458 	}
459 
460 cleanup:
461 	if (result == ISC_R_NOMORE || result == ISC_R_NOTFOUND) {
462 		result = ISC_R_SUCCESS;
463 	}
464 	if (result != ISC_R_SUCCESS && answer == ISC_R_SUCCESS) {
465 		answer = result;
466 	}
467 	if (node != NULL) {
468 		dns_db_detachnode(db, &node);
469 	}
470 	if (iter != NULL) {
471 		dns_dbiterator_destroy(&iter);
472 	}
473 	if (top != NULL) {
474 		dns_db_detachnode(db, &top);
475 	}
476 
477 	return answer;
478 }
479 
480 isc_result_t
481 dns_cache_flushname(dns_cache_t *cache, const dns_name_t *name) {
482 	return dns_cache_flushnode(cache, name, false);
483 }
484 
485 isc_result_t
486 dns_cache_flushnode(dns_cache_t *cache, const dns_name_t *name, bool tree) {
487 	isc_result_t result;
488 	dns_dbnode_t *node = NULL;
489 	dns_db_t *db = NULL;
490 
491 	if (tree && dns_name_equal(name, dns_rootname)) {
492 		return dns_cache_flush(cache);
493 	}
494 
495 	LOCK(&cache->lock);
496 	if (cache->db != NULL) {
497 		dns_db_attach(cache->db, &db);
498 	}
499 	UNLOCK(&cache->lock);
500 	if (db == NULL) {
501 		return ISC_R_SUCCESS;
502 	}
503 
504 	if (tree) {
505 		result = cleartree(cache->db, name);
506 	} else {
507 		result = dns_db_findnode(cache->db, name, false, &node);
508 		if (result == ISC_R_NOTFOUND) {
509 			result = ISC_R_SUCCESS;
510 			goto cleanup_db;
511 		}
512 		if (result != ISC_R_SUCCESS) {
513 			goto cleanup_db;
514 		}
515 		result = clearnode(cache->db, node);
516 		dns_db_detachnode(cache->db, &node);
517 	}
518 
519 cleanup_db:
520 	dns_db_detach(&db);
521 	return result;
522 }
523 
524 isc_stats_t *
525 dns_cache_getstats(dns_cache_t *cache) {
526 	REQUIRE(VALID_CACHE(cache));
527 	return cache->stats;
528 }
529 
530 void
531 dns_cache_updatestats(dns_cache_t *cache, isc_result_t result) {
532 	REQUIRE(VALID_CACHE(cache));
533 	if (cache->stats == NULL) {
534 		return;
535 	}
536 
537 	switch (result) {
538 	case ISC_R_SUCCESS:
539 	case DNS_R_NCACHENXDOMAIN:
540 	case DNS_R_NCACHENXRRSET:
541 	case DNS_R_CNAME:
542 	case DNS_R_DNAME:
543 	case DNS_R_GLUE:
544 	case DNS_R_ZONECUT:
545 	case DNS_R_COVERINGNSEC:
546 		isc_stats_increment(cache->stats,
547 				    dns_cachestatscounter_queryhits);
548 		break;
549 	default:
550 		isc_stats_increment(cache->stats,
551 				    dns_cachestatscounter_querymisses);
552 	}
553 }
554 
555 void
556 dns_cache_setmaxrrperset(dns_cache_t *cache, uint32_t value) {
557 	REQUIRE(VALID_CACHE(cache));
558 
559 	cache->maxrrperset = value;
560 	if (cache->db != NULL) {
561 		dns_db_setmaxrrperset(cache->db, value);
562 	}
563 }
564 
565 void
566 dns_cache_setmaxtypepername(dns_cache_t *cache, uint32_t value) {
567 	REQUIRE(VALID_CACHE(cache));
568 
569 	cache->maxtypepername = value;
570 	if (cache->db != NULL) {
571 		dns_db_setmaxtypepername(cache->db, value);
572 	}
573 }
574 
575 /*
576  * XXX: Much of the following code has been copied in from statschannel.c.
577  * We should refactor this into a generic function in stats.c that can be
578  * called from both places.
579  */
580 typedef struct cache_dumparg {
581 	isc_statsformat_t type;
582 	void *arg;		 /* type dependent argument */
583 	int ncounters;		 /* for general statistics */
584 	int *counterindices;	 /* for general statistics */
585 	uint64_t *countervalues; /* for general statistics */
586 	isc_result_t result;
587 } cache_dumparg_t;
588 
589 static void
590 getcounter(isc_statscounter_t counter, uint64_t val, void *arg) {
591 	cache_dumparg_t *dumparg = arg;
592 
593 	REQUIRE(counter < dumparg->ncounters);
594 	dumparg->countervalues[counter] = val;
595 }
596 
597 static void
598 getcounters(isc_stats_t *stats, isc_statsformat_t type, int ncounters,
599 	    int *indices, uint64_t *values) {
600 	cache_dumparg_t dumparg;
601 
602 	memset(values, 0, sizeof(values[0]) * ncounters);
603 
604 	dumparg.type = type;
605 	dumparg.ncounters = ncounters;
606 	dumparg.counterindices = indices;
607 	dumparg.countervalues = values;
608 
609 	isc_stats_dump(stats, getcounter, &dumparg, ISC_STATSDUMP_VERBOSE);
610 }
611 
612 void
613 dns_cache_dumpstats(dns_cache_t *cache, FILE *fp) {
614 	int indices[dns_cachestatscounter_max];
615 	uint64_t values[dns_cachestatscounter_max];
616 
617 	REQUIRE(VALID_CACHE(cache));
618 
619 	getcounters(cache->stats, isc_statsformat_file,
620 		    dns_cachestatscounter_max, indices, values);
621 
622 	fprintf(fp, "%20" PRIu64 " %s\n", values[dns_cachestatscounter_hits],
623 		"cache hits");
624 	fprintf(fp, "%20" PRIu64 " %s\n", values[dns_cachestatscounter_misses],
625 		"cache misses");
626 	fprintf(fp, "%20" PRIu64 " %s\n",
627 		values[dns_cachestatscounter_queryhits],
628 		"cache hits (from query)");
629 	fprintf(fp, "%20" PRIu64 " %s\n",
630 		values[dns_cachestatscounter_querymisses],
631 		"cache misses (from query)");
632 	fprintf(fp, "%20" PRIu64 " %s\n",
633 		values[dns_cachestatscounter_deletelru],
634 		"cache records deleted due to memory exhaustion");
635 	fprintf(fp, "%20" PRIu64 " %s\n",
636 		values[dns_cachestatscounter_deletettl],
637 		"cache records deleted due to TTL expiration");
638 	fprintf(fp, "%20" PRIu64 " %s\n",
639 		values[dns_cachestatscounter_coveringnsec],
640 		"covering nsec returned");
641 	fprintf(fp, "%20u %s\n", dns_db_nodecount(cache->db, dns_dbtree_main),
642 		"cache database nodes");
643 	fprintf(fp, "%20u %s\n", dns_db_nodecount(cache->db, dns_dbtree_nsec),
644 		"cache NSEC auxiliary database nodes");
645 	fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)dns_db_hashsize(cache->db),
646 		"cache database hash buckets");
647 
648 	fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_inuse(cache->tmctx),
649 		"cache tree memory in use");
650 
651 	fprintf(fp, "%20" PRIu64 " %s\n", (uint64_t)isc_mem_inuse(cache->hmctx),
652 		"cache heap memory in use");
653 }
654 
655 #ifdef HAVE_LIBXML2
656 #define TRY0(a)                     \
657 	do {                        \
658 		xmlrc = (a);        \
659 		if (xmlrc < 0)      \
660 			goto error; \
661 	} while (0)
662 static int
663 renderstat(const char *name, uint64_t value, xmlTextWriterPtr writer) {
664 	int xmlrc;
665 
666 	TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "counter"));
667 	TRY0(xmlTextWriterWriteAttribute(writer, ISC_XMLCHAR "name",
668 					 ISC_XMLCHAR name));
669 	TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", value));
670 	TRY0(xmlTextWriterEndElement(writer)); /* counter */
671 
672 error:
673 	return xmlrc;
674 }
675 
676 int
677 dns_cache_renderxml(dns_cache_t *cache, void *writer0) {
678 	int indices[dns_cachestatscounter_max];
679 	uint64_t values[dns_cachestatscounter_max];
680 	int xmlrc;
681 	xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0;
682 
683 	REQUIRE(VALID_CACHE(cache));
684 
685 	getcounters(cache->stats, isc_statsformat_file,
686 		    dns_cachestatscounter_max, indices, values);
687 	TRY0(renderstat("CacheHits", values[dns_cachestatscounter_hits],
688 			writer));
689 	TRY0(renderstat("CacheMisses", values[dns_cachestatscounter_misses],
690 			writer));
691 	TRY0(renderstat("QueryHits", values[dns_cachestatscounter_queryhits],
692 			writer));
693 	TRY0(renderstat("QueryMisses",
694 			values[dns_cachestatscounter_querymisses], writer));
695 	TRY0(renderstat("DeleteLRU", values[dns_cachestatscounter_deletelru],
696 			writer));
697 	TRY0(renderstat("DeleteTTL", values[dns_cachestatscounter_deletettl],
698 			writer));
699 	TRY0(renderstat("CoveringNSEC",
700 			values[dns_cachestatscounter_coveringnsec], writer));
701 
702 	TRY0(renderstat("CacheNodes",
703 			dns_db_nodecount(cache->db, dns_dbtree_main), writer));
704 	TRY0(renderstat("CacheNSECNodes",
705 			dns_db_nodecount(cache->db, dns_dbtree_nsec), writer));
706 	TRY0(renderstat("CacheBuckets", dns_db_hashsize(cache->db), writer));
707 
708 	TRY0(renderstat("TreeMemInUse", isc_mem_inuse(cache->tmctx), writer));
709 
710 	TRY0(renderstat("HeapMemInUse", isc_mem_inuse(cache->hmctx), writer));
711 error:
712 	return xmlrc;
713 }
714 #endif /* ifdef HAVE_LIBXML2 */
715 
716 #ifdef HAVE_JSON_C
717 #define CHECKMEM(m)                              \
718 	do {                                     \
719 		if (m == NULL) {                 \
720 			result = ISC_R_NOMEMORY; \
721 			goto error;              \
722 		}                                \
723 	} while (0)
724 
725 isc_result_t
726 dns_cache_renderjson(dns_cache_t *cache, void *cstats0) {
727 	isc_result_t result = ISC_R_SUCCESS;
728 	int indices[dns_cachestatscounter_max];
729 	uint64_t values[dns_cachestatscounter_max];
730 	json_object *obj;
731 	json_object *cstats = (json_object *)cstats0;
732 
733 	REQUIRE(VALID_CACHE(cache));
734 
735 	getcounters(cache->stats, isc_statsformat_file,
736 		    dns_cachestatscounter_max, indices, values);
737 
738 	obj = json_object_new_int64(values[dns_cachestatscounter_hits]);
739 	CHECKMEM(obj);
740 	json_object_object_add(cstats, "CacheHits", obj);
741 
742 	obj = json_object_new_int64(values[dns_cachestatscounter_misses]);
743 	CHECKMEM(obj);
744 	json_object_object_add(cstats, "CacheMisses", obj);
745 
746 	obj = json_object_new_int64(values[dns_cachestatscounter_queryhits]);
747 	CHECKMEM(obj);
748 	json_object_object_add(cstats, "QueryHits", obj);
749 
750 	obj = json_object_new_int64(values[dns_cachestatscounter_querymisses]);
751 	CHECKMEM(obj);
752 	json_object_object_add(cstats, "QueryMisses", obj);
753 
754 	obj = json_object_new_int64(values[dns_cachestatscounter_deletelru]);
755 	CHECKMEM(obj);
756 	json_object_object_add(cstats, "DeleteLRU", obj);
757 
758 	obj = json_object_new_int64(values[dns_cachestatscounter_deletettl]);
759 	CHECKMEM(obj);
760 	json_object_object_add(cstats, "DeleteTTL", obj);
761 
762 	obj = json_object_new_int64(values[dns_cachestatscounter_coveringnsec]);
763 	CHECKMEM(obj);
764 	json_object_object_add(cstats, "CoveringNSEC", obj);
765 
766 	obj = json_object_new_int64(
767 		dns_db_nodecount(cache->db, dns_dbtree_main));
768 	CHECKMEM(obj);
769 	json_object_object_add(cstats, "CacheNodes", obj);
770 
771 	obj = json_object_new_int64(
772 		dns_db_nodecount(cache->db, dns_dbtree_nsec));
773 	CHECKMEM(obj);
774 	json_object_object_add(cstats, "CacheNSECNodes", obj);
775 
776 	obj = json_object_new_int64(dns_db_hashsize(cache->db));
777 	CHECKMEM(obj);
778 	json_object_object_add(cstats, "CacheBuckets", obj);
779 
780 	obj = json_object_new_int64(isc_mem_inuse(cache->tmctx));
781 	CHECKMEM(obj);
782 	json_object_object_add(cstats, "TreeMemInUse", obj);
783 
784 	obj = json_object_new_int64(isc_mem_inuse(cache->hmctx));
785 	CHECKMEM(obj);
786 	json_object_object_add(cstats, "HeapMemInUse", obj);
787 
788 	result = ISC_R_SUCCESS;
789 error:
790 	return result;
791 }
792 #endif /* ifdef HAVE_JSON_C */
793