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