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