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