sysctlgetmibinfo.c revision 1.6.8.2 1 /* $NetBSD: sysctlgetmibinfo.c,v 1.6.8.2 2008/04/29 06:53:02 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.8.2 2008/04/29 06:53:02 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