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