sysctlgetmibinfo.c revision 1.2.2.1 1 /* $NetBSD: sysctlgetmibinfo.c,v 1.2.2.1 2004/04/08 19:38:58 jdc 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