xref: /netbsd-src/lib/libc/gen/sysctlgetmibinfo.c (revision 3816d47b2c42fcd6e549e3407f842a5b1a1d23ad)
1 /*	$NetBSD: sysctlgetmibinfo.c,v 1.7 2009/02/12 04:13:08 lukem Exp $ */
2 
3 /*-
4  * Copyright (c) 2003,2004 The NetBSD Foundation, Inc.
5  *	All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Andrew Brown.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 #include <sys/cdefs.h>
33 #if defined(LIBC_SCCS) && !defined(lint)
34 __RCSID("$NetBSD: sysctlgetmibinfo.c,v 1.7 2009/02/12 04:13:08 lukem Exp $");
35 #endif /* LIBC_SCCS and not lint */
36 
37 #include "namespace.h"
38 #ifdef _REENTRANT
39 #include "reentrant.h"
40 #endif /* _REENTRANT */
41 #include <sys/param.h>
42 #include <sys/sysctl.h>
43 
44 #include <errno.h>
45 #include <inttypes.h>
46 #include <stdlib.h>
47 #include <string.h>
48 
49 #ifdef __weak_alias
50 __weak_alias(__learn_tree,___learn_tree)
51 __weak_alias(sysctlgetmibinfo,_sysctlgetmibinfo)
52 #endif
53 
54 /*
55  * the place where we attach stuff we learn on the fly, not
56  * necessarily used.
57  */
58 static struct sysctlnode sysctl_mibroot = {
59 #if defined(lint)
60 	/*
61 	 * lint doesn't like my initializers
62 	 */
63 	0
64 #else /* !lint */
65 	.sysctl_flags = SYSCTL_VERSION|CTLFLAG_ROOT|CTLTYPE_NODE,
66 	sysc_init_field(_sysctl_size, sizeof(struct sysctlnode)),
67 	.sysctl_name = "(root)",
68 #endif /* !lint */
69 };
70 
71 /*
72  * routines to handle learning and cleanup
73  */
74 static int compar(const void *, const void *);
75 static void free_children(struct sysctlnode *);
76 static void relearnhead(void);
77 
78 /*
79  * specifically not static since sysctl(8) "borrows" it.
80  */
81 int __learn_tree(int *, u_int, struct sysctlnode *);
82 
83 /*
84  * for ordering nodes -- a query may or may not be given them in
85  * numeric order
86  */
87 static int
88 compar(const void *a, const void *b)
89 {
90 
91 	return (((const struct sysctlnode *)a)->sysctl_num -
92 		((const struct sysctlnode *)b)->sysctl_num);
93 }
94 
95 /*
96  * recursively nukes a branch or an entire tree from the given node
97  */
98 static void
99 free_children(struct sysctlnode *rnode)
100 {
101 	struct sysctlnode *node;
102 
103 	if (rnode == NULL ||
104 	    SYSCTL_TYPE(rnode->sysctl_flags) != CTLTYPE_NODE ||
105 	    rnode->sysctl_child == NULL)
106 		return;
107 
108 	for (node = rnode->sysctl_child;
109 	     node < &rnode->sysctl_child[rnode->sysctl_clen];
110 	     node++) {
111 		free_children(node);
112 	}
113 	free(rnode->sysctl_child);
114 	rnode->sysctl_child = NULL;
115 }
116 
117 /*
118  * verifies that the head of the tree in the kernel is the same as the
119  * head of the tree we already got, integrating new stuff and removing
120  * old stuff, if it's not.
121  */
122 static void
123 relearnhead(void)
124 {
125 	struct sysctlnode *h, *i, *o, qnode;
126 	size_t si, so;
127 	int rc, name, nlen, olen, ni, oi;
128 	uint32_t t;
129 
130 	/*
131 	 * if there's nothing there, there's no need to expend any
132 	 * effort
133 	 */
134 	if (sysctl_mibroot.sysctl_child == NULL)
135 		return;
136 
137 	/*
138 	 * attempt to pull out the head of the tree, starting with the
139 	 * size we have now, and looping if we need more (or less)
140 	 * space
141 	 */
142 	si = 0;
143 	so = sysctl_mibroot.sysctl_clen * sizeof(struct sysctlnode);
144 	name = CTL_QUERY;
145 	memset(&qnode, 0, sizeof(qnode));
146 	qnode.sysctl_flags = SYSCTL_VERSION;
147 	do {
148 		si = so;
149 		h = malloc(si);
150 		rc = sysctl(&name, 1, h, &so, &qnode, sizeof(qnode));
151 		if (rc == -1 && errno != ENOMEM)
152 			return;
153 		if (si < so)
154 			free(h);
155 	} while (si < so);
156 
157 	/*
158 	 * order the new copy of the head
159 	 */
160 	nlen = so / sizeof(struct sysctlnode);
161 	qsort(h, (size_t)nlen, sizeof(struct sysctlnode), compar);
162 
163 	/*
164 	 * verify that everything is the same.  if it is, we don't
165 	 * need to do any more work here.
166 	 */
167 	olen = sysctl_mibroot.sysctl_clen;
168 	rc = (nlen == olen) ? 0 : 1;
169 	o = sysctl_mibroot.sysctl_child;
170 	for (ni = 0; rc == 0 && ni < nlen; ni++) {
171 		if (h[ni].sysctl_num != o[ni].sysctl_num ||
172 		    h[ni].sysctl_ver != o[ni].sysctl_ver)
173 			rc = 1;
174 	}
175 	if (rc == 0) {
176 		free(h);
177 		return;
178 	}
179 
180 	/*
181 	 * something changed.  h will become the new head, and we need
182 	 * pull over any subtrees we already have if they're the same
183 	 * version.
184 	 */
185 	i = h;
186 	ni = oi = 0;
187 	while (ni < nlen && oi < olen) {
188 		/*
189 		 * something was inserted or deleted
190 		 */
191 		if (SYSCTL_TYPE(i[ni].sysctl_flags) == CTLTYPE_NODE)
192 			i[ni].sysctl_child = NULL;
193 		if (i[ni].sysctl_num != o[oi].sysctl_num) {
194 			if (i[ni].sysctl_num < o[oi].sysctl_num) {
195 				ni++;
196 			}
197 			else {
198 				free_children(&o[oi]);
199 				oi++;
200 			}
201 			continue;
202 		}
203 
204 		/*
205 		 * same number, but different version, so throw away
206 		 * any accumulated children
207 		 */
208 		if (i[ni].sysctl_ver != o[oi].sysctl_ver)
209 			free_children(&o[oi]);
210 
211 		/*
212 		 * this node is the same, but we only need to
213 		 * move subtrees.
214 		 */
215 		else if (SYSCTL_TYPE(i[ni].sysctl_flags) == CTLTYPE_NODE) {
216 			/*
217 			 * move subtree to new parent
218 			 */
219 			i[ni].sysctl_clen = o[oi].sysctl_clen;
220 			i[ni].sysctl_csize = o[oi].sysctl_csize;
221 			i[ni].sysctl_child = o[oi].sysctl_child;
222 			/*
223 			 * reparent inherited subtree
224 			 */
225 			for (t = 0;
226 			     i[ni].sysctl_child != NULL &&
227 				     t < i[ni].sysctl_clen;
228 			     t++)
229 				i[ni].sysctl_child[t].sysctl_parent = &i[ni];
230 		}
231 		ni++;
232 		oi++;
233 	}
234 
235 	/*
236 	 * left over new nodes need to have empty subtrees cleared
237 	 */
238 	while (ni < nlen) {
239 		if (SYSCTL_TYPE(i[ni].sysctl_flags) == CTLTYPE_NODE)
240 			i[ni].sysctl_child = NULL;
241 		ni++;
242 	}
243 
244 	/*
245 	 * left over old nodes need to be cleaned out
246 	 */
247 	while (oi < olen) {
248 		free_children(&o[oi]);
249 		oi++;
250 	}
251 
252 	/*
253 	 * pop new head in
254 	 */
255 	sysctl_mibroot.sysctl_clen = nlen;
256 	sysctl_mibroot.sysctl_csize = nlen;
257 	sysctl_mibroot.sysctl_child = h;
258 	free(o);
259 }
260 
261 /*
262  * sucks in the children at a given level and attaches it to the tree.
263  */
264 int
265 __learn_tree(int *name, u_int namelen, struct sysctlnode *pnode)
266 {
267 	struct sysctlnode qnode;
268 	uint32_t rc;
269 	size_t sz;
270 
271 	if (pnode == NULL)
272 		pnode = &sysctl_mibroot;
273 	if (SYSCTL_TYPE(pnode->sysctl_flags) != CTLTYPE_NODE) {
274 		errno = EINVAL;
275 		return (-1);
276 	}
277 	if (pnode->sysctl_child != NULL)
278 		return (0);
279 
280 	if (pnode->sysctl_clen == 0)
281 		sz = SYSCTL_DEFSIZE * sizeof(struct sysctlnode);
282 	else
283 		sz = pnode->sysctl_clen * sizeof(struct sysctlnode);
284 	pnode->sysctl_child = malloc(sz);
285 	if (pnode->sysctl_child == NULL)
286 		return (-1);
287 
288 	name[namelen] = CTL_QUERY;
289 	pnode->sysctl_clen = 0;
290 	pnode->sysctl_csize = 0;
291 	memset(&qnode, 0, sizeof(qnode));
292 	qnode.sysctl_flags = SYSCTL_VERSION;
293 	rc = sysctl(name, namelen + 1, pnode->sysctl_child, &sz,
294 		    &qnode, sizeof(qnode));
295 	if (sz == 0) {
296 		free(pnode->sysctl_child);
297 		pnode->sysctl_child = NULL;
298 		return (rc);
299 	}
300 	if (rc) {
301 		free(pnode->sysctl_child);
302 		pnode->sysctl_child = NULL;
303 		if ((sz % sizeof(struct sysctlnode)) != 0)
304 			errno = EINVAL;
305 		if (errno != ENOMEM)
306 			return (rc);
307 	}
308 
309 	if (pnode->sysctl_child == NULL) {
310 		pnode->sysctl_child = malloc(sz);
311 		if (pnode->sysctl_child == NULL)
312 			return (-1);
313 
314 		rc = sysctl(name, namelen + 1, pnode->sysctl_child, &sz,
315 			    &qnode, sizeof(qnode));
316 		if (rc) {
317 			free(pnode->sysctl_child);
318 			pnode->sysctl_child = NULL;
319 			return (rc);
320 		}
321 	}
322 
323 	/*
324 	 * how many did we get?
325 	 */
326 	pnode->sysctl_clen = sz / sizeof(struct sysctlnode);
327 	pnode->sysctl_csize = sz / sizeof(struct sysctlnode);
328 	if (pnode->sysctl_clen * sizeof(struct sysctlnode) != sz) {
329 		free(pnode->sysctl_child);
330 		pnode->sysctl_child = NULL;
331 		errno = EINVAL;
332 		return (-1);
333 	}
334 
335 	/*
336 	 * you know, the kernel doesn't really keep them in any
337 	 * particular order...just like entries in a directory
338 	 */
339 	qsort(pnode->sysctl_child, pnode->sysctl_clen,
340 	    sizeof(struct sysctlnode), compar);
341 
342 	/*
343 	 * rearrange parent<->child linkage
344 	 */
345 	for (rc = 0; rc < pnode->sysctl_clen; rc++) {
346 		pnode->sysctl_child[rc].sysctl_parent = pnode;
347 		if (SYSCTL_TYPE(pnode->sysctl_child[rc].sysctl_flags) ==
348 		    CTLTYPE_NODE) {
349 			/*
350 			 * these nodes may have children, but we
351 			 * haven't discovered that yet.
352 			 */
353 			pnode->sysctl_child[rc].sysctl_child = NULL;
354 		}
355 		pnode->sysctl_child[rc].sysctl_desc = NULL;
356 	}
357 
358 	return (0);
359 }
360 
361 /*
362  * that's "given name" as a string, the integer form of the name fit
363  * to be passed to sysctl(), "canonicalized name" (optional), and a
364  * pointer to the length of the integer form.  oh, and then a pointer
365  * to the node, in case you (the caller) care.  you can leave them all
366  * NULL except for gname, though that might be rather pointless,
367  * unless all you wanna do is verify that a given name is acceptable.
368  *
369  * returns either 0 (everything was fine) or -1 and sets errno
370  * accordingly.  if errno is set to EAGAIN, we detected a change to
371  * the mib while parsing, and you should try again.  in the case of an
372  * invalid node name, cname will be set to contain the offending name.
373  */
374 #ifdef _REENTRANT
375 static mutex_t sysctl_mutex = MUTEX_INITIALIZER;
376 static int sysctlgetmibinfo_unlocked(const char *, int *, u_int *, char *,
377 				     size_t *, struct sysctlnode **, int);
378 #endif /* __REENTRANT */
379 
380 int
381 sysctlgetmibinfo(const char *gname, int *iname, u_int *namelenp,
382 		 char *cname, size_t *csz, struct sysctlnode **rnode, int v)
383 #ifdef _REENTRANT
384 {
385 	int rc;
386 
387 	mutex_lock(&sysctl_mutex);
388 	rc = sysctlgetmibinfo_unlocked(gname, iname, namelenp, cname, csz,
389 				       rnode, v);
390 	mutex_unlock(&sysctl_mutex);
391 
392 	return (rc);
393 }
394 
395 static int
396 sysctlgetmibinfo_unlocked(const char *gname, int *iname, u_int *namelenp,
397 			  char *cname, size_t *csz, struct sysctlnode **rnode,
398 			  int v)
399 #endif /* _REENTRANT */
400 {
401 	struct sysctlnode *pnode, *node;
402 	int name[CTL_MAXNAME], n, haven;
403 	u_int ni, nl;
404 	intmax_t q;
405 	char sep[2], token[SYSCTL_NAMELEN],
406 		pname[SYSCTL_NAMELEN * CTL_MAXNAME + CTL_MAXNAME];
407 	const char *piece, *dot;
408 	char *t;
409 	size_t l;
410 
411 	if (rnode != NULL) {
412 		if (*rnode == NULL) {
413 			/* XXX later deal with dealing back a sub version */
414 			if (v != SYSCTL_VERSION)
415 				return (EINVAL);
416 
417 			pnode = &sysctl_mibroot;
418 		}
419 		else {
420 			/* this is just someone being silly */
421 			if (SYSCTL_VERS((*rnode)->sysctl_flags) != (uint32_t)v)
422 				return (EINVAL);
423 
424 			/* XXX later deal with other people's trees */
425 			if (SYSCTL_VERS((*rnode)->sysctl_flags) !=
426 			    SYSCTL_VERSION)
427 				return (EINVAL);
428 
429 			pnode = *rnode;
430 		}
431 	}
432 	else
433 		pnode = &sysctl_mibroot;
434 
435 	if (pnode == &sysctl_mibroot)
436 		relearnhead();
437 
438 	nl = ni = 0;
439 	token[0] = '\0';
440 	pname[0] = '\0';
441 	node = NULL;
442 
443 	/*
444 	 * default to using '.' as the separator, but allow '/' as
445 	 * well, and then allow a leading separator
446 	 */
447 	if ((dot = strpbrk(gname, "./")) == NULL)
448 		sep[0] = '.';
449 	else
450 		sep[0] = dot[0];
451 	sep[1] = '\0';
452 	if (gname[0] == sep[0]) {
453 		strlcat(pname, sep, sizeof(pname));
454 		gname++;
455 	}
456 
457 #define COPY_OUT_DATA(t, c, cs, nlp, l) do {			\
458 		if ((c) != NULL && (cs) != NULL)		\
459 			*(cs) = strlcpy((c), (t), *(cs));	\
460 		else if ((cs) != NULL)				\
461 			*(cs) = strlen(t) + 1;			\
462 		if ((nlp) != NULL)				\
463 			*(nlp) = (l);				\
464 	} while (/*CONSTCOND*/0)
465 
466 	piece = gname;
467 	while (piece != NULL && *piece != '\0') {
468 		/*
469 		 * what was i looking for?
470 		 */
471 		dot = strchr(piece, sep[0]);
472 		if (dot == NULL) {
473 			l = strlcpy(token, piece, sizeof(token));
474 			if (l > sizeof(token)) {
475 				COPY_OUT_DATA(piece, cname, csz, namelenp, nl);
476 				errno = ENAMETOOLONG;
477 				return (-1);
478 			}
479 		}
480 		else if (dot - piece > (intptr_t)(sizeof(token) - 1)) {
481 			COPY_OUT_DATA(token, cname, csz, namelenp, nl);
482 			errno = ENAMETOOLONG;
483 			return (-1);
484 		}
485 		else {
486 			strncpy(token, piece, (size_t)(dot - piece));
487 			token[dot - piece] = '\0';
488 		}
489 
490 		/*
491 		 * i wonder if this "token" is an integer?
492 		 */
493 		errno = 0;
494 		q = strtoimax(token, &t, 0);
495 		n = (int)q;
496 		if (errno != 0 || *t != '\0')
497 			haven = 0;
498 		else if (q < INT_MIN || q > UINT_MAX)
499 			haven = 0;
500 		else
501 			haven = 1;
502 
503 		/*
504 		 * make sure i have something to look at
505 		 */
506 		if (SYSCTL_TYPE(pnode->sysctl_flags) != CTLTYPE_NODE) {
507 			if (haven && nl > 0) {
508 				strlcat(pname, sep, sizeof(pname));
509 				goto just_numbers;
510 			}
511 			COPY_OUT_DATA(token, cname, csz, namelenp, nl);
512 			errno = ENOTDIR;
513 			return (-1);
514 		}
515 		if (pnode->sysctl_child == NULL) {
516 			if (__learn_tree(name, nl, pnode) == -1) {
517 				COPY_OUT_DATA(token, cname, csz, namelenp, nl);
518 				return (-1);
519 			}
520 		}
521 		node = pnode->sysctl_child;
522 		if (node == NULL) {
523 			COPY_OUT_DATA(token, cname, csz, namelenp, nl);
524 			errno = ENOENT;
525 			return (-1);
526 		}
527 
528 		/*
529 		 * now...is it there?
530 		 */
531 		for (ni = 0; ni < pnode->sysctl_clen; ni++)
532 			if ((haven && ((n == node[ni].sysctl_num) ||
533 			    (node[ni].sysctl_flags & CTLFLAG_ANYNUMBER))) ||
534 			    strcmp(token, node[ni].sysctl_name) == 0)
535 				break;
536 		if (ni >= pnode->sysctl_clen) {
537 			COPY_OUT_DATA(token, cname, csz, namelenp, nl);
538 			errno = ENOENT;
539 			return (-1);
540 		}
541 
542 		/*
543 		 * ah...it is.
544 		 */
545 		pnode = &node[ni];
546 		if (nl > 0)
547 			strlcat(pname, sep, sizeof(pname));
548 		if (haven && n != pnode->sysctl_num) {
549  just_numbers:
550 			strlcat(pname, token, sizeof(pname));
551 			name[nl] = n;
552 		}
553 		else {
554 			strlcat(pname, pnode->sysctl_name, sizeof(pname));
555 			name[nl] = pnode->sysctl_num;
556 		}
557 		piece = (dot != NULL) ? dot + 1 : NULL;
558 		nl++;
559 		if (nl == CTL_MAXNAME) {
560 			COPY_OUT_DATA(token, cname, csz, namelenp, nl);
561 			errno = ERANGE;
562 			return (-1);
563 		}
564 	}
565 
566 	if (nl == 0) {
567 		if (namelenp != NULL)
568 			*namelenp = 0;
569 		errno = EINVAL;
570 		return (-1);
571 	}
572 
573 	COPY_OUT_DATA(pname, cname, csz, namelenp, nl);
574 	if (iname != NULL && namelenp != NULL)
575 		memcpy(iname, &name[0], MIN(nl, *namelenp) * sizeof(int));
576 	if (namelenp != NULL)
577 		*namelenp = nl;
578 	if (rnode != NULL) {
579 		if (*rnode != NULL)
580 			/*
581 			 * they gave us a private tree to work in, so
582 			 * we give back a pointer into that private
583 			 * tree
584 			 */
585 			*rnode = pnode;
586 		else {
587 			/*
588 			 * they gave us a place to put the node data,
589 			 * so give them a copy
590 			 */
591 			*rnode = malloc(sizeof(struct sysctlnode));
592 			if (*rnode != NULL) {
593 				**rnode = *pnode;
594 				(*rnode)->sysctl_child = NULL;
595 				(*rnode)->sysctl_parent = NULL;
596 			}
597 		}
598 	}
599 
600 	return (0);
601 }
602