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