xref: /netbsd/external/mpl/bind/dist/lib/dns/cache.c (revision 4ac1c27e)
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