xref: /netbsd-src/lib/libc/gen/sysctlgetmibinfo.c (revision 404fbe5fb94ca1e054339640cabb2801ce52dd30)
1 /*	$NetBSD: sysctlgetmibinfo.c,v 1.6 2008/04/29 06:53:01 martin 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.6 2008/04/29 06:53:01 martin 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, t;
128 
129 	/*
130 	 * if there's nothing there, there's no need to expend any
131 	 * effort
132 	 */
133 	if (sysctl_mibroot.sysctl_child == NULL)
134 		return;
135 
136 	/*
137 	 * attempt to pull out the head of the tree, starting with the
138 	 * size we have now, and looping if we need more (or less)
139 	 * space
140 	 */
141 	si = 0;
142 	so = sysctl_mibroot.sysctl_clen * sizeof(struct sysctlnode);
143 	name = CTL_QUERY;
144 	memset(&qnode, 0, sizeof(qnode));
145 	qnode.sysctl_flags = SYSCTL_VERSION;
146 	do {
147 		si = so;
148 		h = malloc(si);
149 		rc = sysctl(&name, 1, h, &so, &qnode, sizeof(qnode));
150 		if (rc == -1 && errno != ENOMEM)
151 			return;
152 		if (si < so)
153 			free(h);
154 	} while (si < so);
155 
156 	/*
157 	 * order the new copy of the head
158 	 */
159 	nlen = so / sizeof(struct sysctlnode);
160 	qsort(h, (size_t)nlen, sizeof(struct sysctlnode), compar);
161 
162 	/*
163 	 * verify that everything is the same.  if it is, we don't
164 	 * need to do any more work here.
165 	 */
166 	olen = sysctl_mibroot.sysctl_clen;
167 	rc = (nlen == olen) ? 0 : 1;
168 	o = sysctl_mibroot.sysctl_child;
169 	for (ni = 0; rc == 0 && ni < nlen; ni++) {
170 		if (h[ni].sysctl_num != o[ni].sysctl_num ||
171 		    h[ni].sysctl_ver != o[ni].sysctl_ver)
172 			rc = 1;
173 	}
174 	if (rc == 0) {
175 		free(h);
176 		return;
177 	}
178 
179 	/*
180 	 * something changed.  h will become the new head, and we need
181 	 * pull over any subtrees we already have if they're the same
182 	 * version.
183 	 */
184 	i = h;
185 	ni = oi = 0;
186 	while (ni < nlen && oi < olen) {
187 		/*
188 		 * something was inserted or deleted
189 		 */
190 		if (SYSCTL_TYPE(i[ni].sysctl_flags) == CTLTYPE_NODE)
191 			i[ni].sysctl_child = NULL;
192 		if (i[ni].sysctl_num != o[oi].sysctl_num) {
193 			if (i[ni].sysctl_num < o[oi].sysctl_num) {
194 				ni++;
195 			}
196 			else {
197 				free_children(&o[oi]);
198 				oi++;
199 			}
200 			continue;
201 		}
202 
203 		/*
204 		 * same number, but different version, so throw away
205 		 * any accumulated children
206 		 */
207 		if (i[ni].sysctl_ver != o[oi].sysctl_ver)
208 			free_children(&o[oi]);
209 
210 		/*
211 		 * this node is the same, but we only need to
212 		 * move subtrees.
213 		 */
214 		else if (SYSCTL_TYPE(i[ni].sysctl_flags) == CTLTYPE_NODE) {
215 			/*
216 			 * move subtree to new parent
217 			 */
218 			i[ni].sysctl_clen = o[oi].sysctl_clen;
219 			i[ni].sysctl_csize = o[oi].sysctl_csize;
220 			i[ni].sysctl_child = o[oi].sysctl_child;
221 			/*
222 			 * reparent inherited subtree
223 			 */
224 			for (t = 0;
225 			     i[ni].sysctl_child != NULL &&
226 				     t < i[ni].sysctl_clen;
227 			     t++)
228 				i[ni].sysctl_child[t].sysctl_parent = &i[ni];
229 		}
230 		ni++;
231 		oi++;
232 	}
233 
234 	/*
235 	 * left over new nodes need to have empty subtrees cleared
236 	 */
237 	while (ni < nlen) {
238 		if (SYSCTL_TYPE(i[ni].sysctl_flags) == CTLTYPE_NODE)
239 			i[ni].sysctl_child = NULL;
240 		ni++;
241 	}
242 
243 	/*
244 	 * left over old nodes need to be cleaned out
245 	 */
246 	while (oi < olen) {
247 		free_children(&o[oi]);
248 		oi++;
249 	}
250 
251 	/*
252 	 * pop new head in
253 	 */
254 	sysctl_mibroot.sysctl_clen = nlen;
255 	sysctl_mibroot.sysctl_csize = nlen;
256 	sysctl_mibroot.sysctl_child = h;
257 	free(o);
258 }
259 
260 /*
261  * sucks in the children at a given level and attaches it to the tree.
262  */
263 int
264 __learn_tree(int *name, u_int namelen, struct sysctlnode *pnode)
265 {
266 	struct sysctlnode qnode;
267 	int rc;
268 	size_t sz;
269 
270 	if (pnode == NULL)
271 		pnode = &sysctl_mibroot;
272 	if (SYSCTL_TYPE(pnode->sysctl_flags) != CTLTYPE_NODE) {
273 		errno = EINVAL;
274 		return (-1);
275 	}
276 	if (pnode->sysctl_child != NULL)
277 		return (0);
278 
279 	if (pnode->sysctl_clen == 0)
280 		sz = SYSCTL_DEFSIZE * sizeof(struct sysctlnode);
281 	else
282 		sz = pnode->sysctl_clen * sizeof(struct sysctlnode);
283 	pnode->sysctl_child = malloc(sz);
284 	if (pnode->sysctl_child == NULL)
285 		return (-1);
286 
287 	name[namelen] = CTL_QUERY;
288 	pnode->sysctl_clen = 0;
289 	pnode->sysctl_csize = 0;
290 	memset(&qnode, 0, sizeof(qnode));
291 	qnode.sysctl_flags = SYSCTL_VERSION;
292 	rc = sysctl(name, namelen + 1, pnode->sysctl_child, &sz,
293 		    &qnode, sizeof(qnode));
294 	if (sz == 0) {
295 		free(pnode->sysctl_child);
296 		pnode->sysctl_child = NULL;
297 		return (rc);
298 	}
299 	if (rc) {
300 		free(pnode->sysctl_child);
301 		pnode->sysctl_child = NULL;
302 		if ((sz % sizeof(struct sysctlnode)) != 0)
303 			errno = EINVAL;
304 		if (errno != ENOMEM)
305 			return (rc);
306 	}
307 
308 	if (pnode->sysctl_child == NULL) {
309 		pnode->sysctl_child = malloc(sz);
310 		if (pnode->sysctl_child == NULL)
311 			return (-1);
312 
313 		rc = sysctl(name, namelen + 1, pnode->sysctl_child, &sz,
314 			    &qnode, sizeof(qnode));
315 		if (rc) {
316 			free(pnode->sysctl_child);
317 			pnode->sysctl_child = NULL;
318 			return (rc);
319 		}
320 	}
321 
322 	/*
323 	 * how many did we get?
324 	 */
325 	pnode->sysctl_clen = sz / sizeof(struct sysctlnode);
326 	pnode->sysctl_csize = sz / sizeof(struct sysctlnode);
327 	if (pnode->sysctl_clen * sizeof(struct sysctlnode) != sz) {
328 		free(pnode->sysctl_child);
329 		pnode->sysctl_child = NULL;
330 		errno = EINVAL;
331 		return (-1);
332 	}
333 
334 	/*
335 	 * you know, the kernel doesn't really keep them in any
336 	 * particular order...just like entries in a directory
337 	 */
338 	qsort(pnode->sysctl_child, pnode->sysctl_clen,
339 	    sizeof(struct sysctlnode), compar);
340 
341 	/*
342 	 * rearrange parent<->child linkage
343 	 */
344 	for (rc = 0; rc < pnode->sysctl_clen; rc++) {
345 		pnode->sysctl_child[rc].sysctl_parent = pnode;
346 		if (SYSCTL_TYPE(pnode->sysctl_child[rc].sysctl_flags) ==
347 		    CTLTYPE_NODE) {
348 			/*
349 			 * these nodes may have children, but we
350 			 * haven't discovered that yet.
351 			 */
352 			pnode->sysctl_child[rc].sysctl_child = NULL;
353 		}
354 		pnode->sysctl_child[rc].sysctl_desc = NULL;
355 	}
356 
357 	return (0);
358 }
359 
360 /*
361  * that's "given name" as a string, the integer form of the name fit
362  * to be passed to sysctl(), "canonicalized name" (optional), and a
363  * pointer to the length of the integer form.  oh, and then a pointer
364  * to the node, in case you (the caller) care.  you can leave them all
365  * NULL except for gname, though that might be rather pointless,
366  * unless all you wanna do is verify that a given name is acceptable.
367  *
368  * returns either 0 (everything was fine) or -1 and sets errno
369  * accordingly.  if errno is set to EAGAIN, we detected a change to
370  * the mib while parsing, and you should try again.  in the case of an
371  * invalid node name, cname will be set to contain the offending name.
372  */
373 #ifdef _REENTRANT
374 static mutex_t sysctl_mutex = MUTEX_INITIALIZER;
375 static int sysctlgetmibinfo_unlocked(const char *, int *, u_int *, char *,
376 				     size_t *, struct sysctlnode **, int);
377 #endif /* __REENTRANT */
378 
379 int
380 sysctlgetmibinfo(const char *gname, int *iname, u_int *namelenp,
381 		 char *cname, size_t *csz, struct sysctlnode **rnode, int v)
382 #ifdef _REENTRANT
383 {
384 	int rc;
385 
386 	mutex_lock(&sysctl_mutex);
387 	rc = sysctlgetmibinfo_unlocked(gname, iname, namelenp, cname, csz,
388 				       rnode, v);
389 	mutex_unlock(&sysctl_mutex);
390 
391 	return (rc);
392 }
393 
394 static int
395 sysctlgetmibinfo_unlocked(const char *gname, int *iname, u_int *namelenp,
396 			  char *cname, size_t *csz, struct sysctlnode **rnode,
397 			  int v)
398 #endif /* _REENTRANT */
399 {
400 	struct sysctlnode *pnode, *node;
401 	int name[CTL_MAXNAME], ni, n, haven;
402 	u_int nl;
403 	intmax_t q;
404 	char sep[2], token[SYSCTL_NAMELEN],
405 		pname[SYSCTL_NAMELEN * CTL_MAXNAME + CTL_MAXNAME];
406 	const char *piece, *dot;
407 	char *t;
408 	size_t l;
409 
410 	if (rnode != NULL) {
411 		if (*rnode == NULL) {
412 			/* XXX later deal with dealing back a sub version */
413 			if (v != SYSCTL_VERSION)
414 				return (EINVAL);
415 
416 			pnode = &sysctl_mibroot;
417 		}
418 		else {
419 			/* this is just someone being silly */
420 			if (SYSCTL_VERS((*rnode)->sysctl_flags) != v)
421 				return (EINVAL);
422 
423 			/* XXX later deal with other people's trees */
424 			if (SYSCTL_VERS((*rnode)->sysctl_flags) !=
425 			    SYSCTL_VERSION)
426 				return (EINVAL);
427 
428 			pnode = *rnode;
429 		}
430 	}
431 	else
432 		pnode = &sysctl_mibroot;
433 
434 	if (pnode == &sysctl_mibroot)
435 		relearnhead();
436 
437 	nl = ni = 0;
438 	token[0] = '\0';
439 	pname[0] = '\0';
440 	node = NULL;
441 
442 	/*
443 	 * default to using '.' as the separator, but allow '/' as
444 	 * well, and then allow a leading separator
445 	 */
446 	if ((dot = strpbrk(gname, "./")) == NULL)
447 		sep[0] = '.';
448 	else
449 		sep[0] = dot[0];
450 	sep[1] = '\0';
451 	if (gname[0] == sep[0]) {
452 		strlcat(pname, sep, sizeof(pname));
453 		gname++;
454 	}
455 
456 #define COPY_OUT_DATA(t, c, cs, nlp, l) do {			\
457 		if ((c) != NULL && (cs) != NULL)		\
458 			*(cs) = strlcpy((c), (t), *(cs));	\
459 		else if ((cs) != NULL)				\
460 			*(cs) = strlen(t) + 1;			\
461 		if ((nlp) != NULL)				\
462 			*(nlp) = (l);				\
463 	} while (/*CONSTCOND*/0)
464 
465 	piece = gname;
466 	while (piece != NULL && *piece != '\0') {
467 		/*
468 		 * what was i looking for?
469 		 */
470 		dot = strchr(piece, sep[0]);
471 		if (dot == NULL) {
472 			l = strlcpy(token, piece, sizeof(token));
473 			if (l > sizeof(token)) {
474 				COPY_OUT_DATA(piece, cname, csz, namelenp, nl);
475 				errno = ENAMETOOLONG;
476 				return (-1);
477 			}
478 		}
479 		else if (dot - piece > sizeof(token) - 1) {
480 			COPY_OUT_DATA(token, cname, csz, namelenp, nl);
481 			errno = ENAMETOOLONG;
482 			return (-1);
483 		}
484 		else {
485 			strncpy(token, piece, (size_t)(dot - piece));
486 			token[dot - piece] = '\0';
487 		}
488 
489 		/*
490 		 * i wonder if this "token" is an integer?
491 		 */
492 		errno = 0;
493 		q = strtoimax(token, &t, 0);
494 		n = (int)q;
495 		if (errno != 0 || *t != '\0')
496 			haven = 0;
497 		else if (q < INT_MIN || q > UINT_MAX)
498 			haven = 0;
499 		else
500 			haven = 1;
501 
502 		/*
503 		 * make sure i have something to look at
504 		 */
505 		if (SYSCTL_TYPE(pnode->sysctl_flags) != CTLTYPE_NODE) {
506 			if (haven && nl > 0) {
507 				strlcat(pname, sep, sizeof(pname));
508 				goto just_numbers;
509 			}
510 			COPY_OUT_DATA(token, cname, csz, namelenp, nl);
511 			errno = ENOTDIR;
512 			return (-1);
513 		}
514 		if (pnode->sysctl_child == NULL) {
515 			if (__learn_tree(name, nl, pnode) == -1) {
516 				COPY_OUT_DATA(token, cname, csz, namelenp, nl);
517 				return (-1);
518 			}
519 		}
520 		node = pnode->sysctl_child;
521 		if (node == NULL) {
522 			COPY_OUT_DATA(token, cname, csz, namelenp, nl);
523 			errno = ENOENT;
524 			return (-1);
525 		}
526 
527 		/*
528 		 * now...is it there?
529 		 */
530 		for (ni = 0; ni < pnode->sysctl_clen; ni++)
531 			if ((haven && ((n == node[ni].sysctl_num) ||
532 			    (node[ni].sysctl_flags & CTLFLAG_ANYNUMBER))) ||
533 			    strcmp(token, node[ni].sysctl_name) == 0)
534 				break;
535 		if (ni >= pnode->sysctl_clen) {
536 			COPY_OUT_DATA(token, cname, csz, namelenp, nl);
537 			errno = ENOENT;
538 			return (-1);
539 		}
540 
541 		/*
542 		 * ah...it is.
543 		 */
544 		pnode = &node[ni];
545 		if (nl > 0)
546 			strlcat(pname, sep, sizeof(pname));
547 		if (haven && n != pnode->sysctl_num) {
548  just_numbers:
549 			strlcat(pname, token, sizeof(pname));
550 			name[nl] = n;
551 		}
552 		else {
553 			strlcat(pname, pnode->sysctl_name, sizeof(pname));
554 			name[nl] = pnode->sysctl_num;
555 		}
556 		piece = (dot != NULL) ? dot + 1 : NULL;
557 		nl++;
558 		if (nl == CTL_MAXNAME) {
559 			COPY_OUT_DATA(token, cname, csz, namelenp, nl);
560 			errno = ERANGE;
561 			return (-1);
562 		}
563 	}
564 
565 	if (nl == 0) {
566 		if (namelenp != NULL)
567 			*namelenp = 0;
568 		errno = EINVAL;
569 		return (-1);
570 	}
571 
572 	COPY_OUT_DATA(pname, cname, csz, namelenp, nl);
573 	if (iname != NULL && namelenp != NULL)
574 		memcpy(iname, &name[0], MIN(nl, *namelenp) * sizeof(int));
575 	if (namelenp != NULL)
576 		*namelenp = nl;
577 	if (rnode != NULL) {
578 		if (*rnode != NULL)
579 			/*
580 			 * they gave us a private tree to work in, so
581 			 * we give back a pointer into that private
582 			 * tree
583 			 */
584 			*rnode = pnode;
585 		else {
586 			/*
587 			 * they gave us a place to put the node data,
588 			 * so give them a copy
589 			 */
590 			*rnode = malloc(sizeof(struct sysctlnode));
591 			if (*rnode != NULL) {
592 				**rnode = *pnode;
593 				(*rnode)->sysctl_child = NULL;
594 				(*rnode)->sysctl_parent = NULL;
595 			}
596 		}
597 	}
598 
599 	return (0);
600 }
601