sysctlgetmibinfo.c revision 1.4 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