1 /*
2  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
7  *
8  * See the COPYRIGHT file distributed with this work for additional
9  * information regarding copyright ownership.
10  */
11 
12 #include <config.h>
13 
14 #include <stdbool.h>
15 
16 #include <isc/mem.h>
17 #include <isc/rwlock.h>
18 #include <isc/util.h>
19 
20 #include <dns/dbtable.h>
21 #include <dns/db.h>
22 #include <dns/rbt.h>
23 #include <dns/result.h>
24 
25 struct dns_dbtable {
26 	/* Unlocked. */
27 	unsigned int		magic;
28 	isc_mem_t *		mctx;
29 	dns_rdataclass_t	rdclass;
30 	isc_mutex_t		lock;
31 	isc_rwlock_t		tree_lock;
32 	/* Locked by lock. */
33 	unsigned int		references;
34 	/* Locked by tree_lock. */
35 	dns_rbt_t *		rbt;
36 	dns_db_t *		default_db;
37 };
38 
39 #define DBTABLE_MAGIC		ISC_MAGIC('D', 'B', '-', '-')
40 #define VALID_DBTABLE(dbtable)	ISC_MAGIC_VALID(dbtable, DBTABLE_MAGIC)
41 
42 static void
dbdetach(void * data,void * arg)43 dbdetach(void *data, void *arg) {
44 	dns_db_t *db = data;
45 
46 	UNUSED(arg);
47 
48 	dns_db_detach(&db);
49 }
50 
51 isc_result_t
dns_dbtable_create(isc_mem_t * mctx,dns_rdataclass_t rdclass,dns_dbtable_t ** dbtablep)52 dns_dbtable_create(isc_mem_t *mctx, dns_rdataclass_t rdclass,
53 		   dns_dbtable_t **dbtablep)
54 {
55 	dns_dbtable_t *dbtable;
56 	isc_result_t result;
57 
58 	REQUIRE(mctx != NULL);
59 	REQUIRE(dbtablep != NULL && *dbtablep == NULL);
60 
61 	dbtable = (dns_dbtable_t *)isc_mem_get(mctx, sizeof(*dbtable));
62 	if (dbtable == NULL)
63 		return (ISC_R_NOMEMORY);
64 
65 	dbtable->rbt = NULL;
66 	result = dns_rbt_create(mctx, dbdetach, NULL, &dbtable->rbt);
67 	if (result != ISC_R_SUCCESS)
68 		goto clean1;
69 
70 	result = isc_mutex_init(&dbtable->lock);
71 	if (result != ISC_R_SUCCESS)
72 		goto clean2;
73 
74 	result = isc_rwlock_init(&dbtable->tree_lock, 0, 0);
75 	if (result != ISC_R_SUCCESS)
76 		goto clean3;
77 
78 	dbtable->default_db = NULL;
79 	dbtable->mctx = NULL;
80 	isc_mem_attach(mctx, &dbtable->mctx);
81 	dbtable->rdclass = rdclass;
82 	dbtable->magic = DBTABLE_MAGIC;
83 	dbtable->references = 1;
84 
85 	*dbtablep = dbtable;
86 
87 	return (ISC_R_SUCCESS);
88 
89  clean3:
90 	DESTROYLOCK(&dbtable->lock);
91 
92  clean2:
93 	dns_rbt_destroy(&dbtable->rbt);
94 
95  clean1:
96 	isc_mem_putanddetach(&mctx, dbtable, sizeof(*dbtable));
97 
98 	return (result);
99 }
100 
101 static inline void
dbtable_free(dns_dbtable_t * dbtable)102 dbtable_free(dns_dbtable_t *dbtable) {
103 	/*
104 	 * Caller must ensure that it is safe to call.
105 	 */
106 
107 	RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
108 
109 	if (dbtable->default_db != NULL)
110 		dns_db_detach(&dbtable->default_db);
111 
112 	dns_rbt_destroy(&dbtable->rbt);
113 
114 	RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
115 
116 	isc_rwlock_destroy(&dbtable->tree_lock);
117 
118 	dbtable->magic = 0;
119 
120 	isc_mem_putanddetach(&dbtable->mctx, dbtable, sizeof(*dbtable));
121 }
122 
123 void
dns_dbtable_attach(dns_dbtable_t * source,dns_dbtable_t ** targetp)124 dns_dbtable_attach(dns_dbtable_t *source, dns_dbtable_t **targetp) {
125 	REQUIRE(VALID_DBTABLE(source));
126 	REQUIRE(targetp != NULL && *targetp == NULL);
127 
128 	LOCK(&source->lock);
129 
130 	INSIST(source->references > 0);
131 	source->references++;
132 	INSIST(source->references != 0);
133 
134 	UNLOCK(&source->lock);
135 
136 	*targetp = source;
137 }
138 
139 void
dns_dbtable_detach(dns_dbtable_t ** dbtablep)140 dns_dbtable_detach(dns_dbtable_t **dbtablep) {
141 	dns_dbtable_t *dbtable;
142 	bool free_dbtable = false;
143 
144 	REQUIRE(dbtablep != NULL);
145 	dbtable = *dbtablep;
146 	REQUIRE(VALID_DBTABLE(dbtable));
147 
148 	LOCK(&dbtable->lock);
149 
150 	INSIST(dbtable->references > 0);
151 	dbtable->references--;
152 	if (dbtable->references == 0)
153 		free_dbtable = true;
154 
155 	UNLOCK(&dbtable->lock);
156 
157 	if (free_dbtable)
158 		dbtable_free(dbtable);
159 
160 	*dbtablep = NULL;
161 }
162 
163 isc_result_t
dns_dbtable_add(dns_dbtable_t * dbtable,dns_db_t * db)164 dns_dbtable_add(dns_dbtable_t *dbtable, dns_db_t *db) {
165 	isc_result_t result;
166 	dns_db_t *dbclone;
167 
168 	REQUIRE(VALID_DBTABLE(dbtable));
169 	REQUIRE(dns_db_class(db) == dbtable->rdclass);
170 
171 	dbclone = NULL;
172 	dns_db_attach(db, &dbclone);
173 
174 	RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
175 	result = dns_rbt_addname(dbtable->rbt, dns_db_origin(dbclone), dbclone);
176 	RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
177 
178 	return (result);
179 }
180 
181 void
dns_dbtable_remove(dns_dbtable_t * dbtable,dns_db_t * db)182 dns_dbtable_remove(dns_dbtable_t *dbtable, dns_db_t *db) {
183 	dns_db_t *stored_data = NULL;
184 	isc_result_t result;
185 	dns_name_t *name;
186 
187 	REQUIRE(VALID_DBTABLE(dbtable));
188 
189 	name = dns_db_origin(db);
190 
191 	/*
192 	 * There is a requirement that the association of name with db
193 	 * be verified.  With the current rbt.c this is expensive to do,
194 	 * because effectively two find operations are being done, but
195 	 * deletion is relatively infrequent.
196 	 * XXXDCL ... this could be cheaper now with dns_rbt_deletenode.
197 	 */
198 
199 	RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
200 
201 	result = dns_rbt_findname(dbtable->rbt, name, 0, NULL,
202 				  (void **) (void *)&stored_data);
203 
204 	if (result == ISC_R_SUCCESS) {
205 		INSIST(stored_data == db);
206 
207 		(void)dns_rbt_deletename(dbtable->rbt, name, false);
208 	}
209 
210 	RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
211 }
212 
213 void
dns_dbtable_adddefault(dns_dbtable_t * dbtable,dns_db_t * db)214 dns_dbtable_adddefault(dns_dbtable_t *dbtable, dns_db_t *db) {
215 	REQUIRE(VALID_DBTABLE(dbtable));
216 	REQUIRE(dbtable->default_db == NULL);
217 	REQUIRE(dns_name_compare(dns_db_origin(db), dns_rootname) == 0);
218 
219 	RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
220 
221 	dbtable->default_db = NULL;
222 	dns_db_attach(db, &dbtable->default_db);
223 
224 	RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
225 }
226 
227 void
dns_dbtable_getdefault(dns_dbtable_t * dbtable,dns_db_t ** dbp)228 dns_dbtable_getdefault(dns_dbtable_t *dbtable, dns_db_t **dbp) {
229 	REQUIRE(VALID_DBTABLE(dbtable));
230 	REQUIRE(dbp != NULL && *dbp == NULL);
231 
232 	RWLOCK(&dbtable->tree_lock, isc_rwlocktype_read);
233 
234 	dns_db_attach(dbtable->default_db, dbp);
235 
236 	RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_read);
237 }
238 
239 void
dns_dbtable_removedefault(dns_dbtable_t * dbtable)240 dns_dbtable_removedefault(dns_dbtable_t *dbtable) {
241 	REQUIRE(VALID_DBTABLE(dbtable));
242 
243 	RWLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
244 
245 	dns_db_detach(&dbtable->default_db);
246 
247 	RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_write);
248 }
249 
250 isc_result_t
dns_dbtable_find(dns_dbtable_t * dbtable,dns_name_t * name,unsigned int options,dns_db_t ** dbp)251 dns_dbtable_find(dns_dbtable_t *dbtable, dns_name_t *name,
252 		 unsigned int options, dns_db_t **dbp)
253 {
254 	dns_db_t *stored_data = NULL;
255 	isc_result_t result;
256 	unsigned int rbtoptions = 0;
257 
258 	REQUIRE(dbp != NULL && *dbp == NULL);
259 
260 	if ((options & DNS_DBTABLEFIND_NOEXACT) != 0)
261 		rbtoptions |= DNS_RBTFIND_NOEXACT;
262 
263 	RWLOCK(&dbtable->tree_lock, isc_rwlocktype_read);
264 
265 	result = dns_rbt_findname(dbtable->rbt, name, rbtoptions, NULL,
266 				  (void **) (void *)&stored_data);
267 
268 	if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH)
269 		dns_db_attach(stored_data, dbp);
270 	else if (dbtable->default_db != NULL) {
271 		dns_db_attach(dbtable->default_db, dbp);
272 		result = DNS_R_PARTIALMATCH;
273 	} else
274 		result = ISC_R_NOTFOUND;
275 
276 	RWUNLOCK(&dbtable->tree_lock, isc_rwlocktype_read);
277 
278 	return (result);
279 }
280