1 /* $NetBSD: sysctlgetmibinfo.c,v 1.10 2012/03/13 21:13:37 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.10 2012/03/13 21:13:37 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 sysc_init_field(_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 281 if (pnode == NULL) 282 pnode = &sysctl_mibroot; 283 if (SYSCTL_TYPE(pnode->sysctl_flags) != CTLTYPE_NODE) { 284 errno = EINVAL; 285 return (-1); 286 } 287 if (pnode->sysctl_child != NULL) 288 return (0); 289 290 if (pnode->sysctl_clen == 0) 291 sz = SYSCTL_DEFSIZE * sizeof(struct sysctlnode); 292 else 293 sz = pnode->sysctl_clen * sizeof(struct sysctlnode); 294 pnode->sysctl_child = malloc(sz); 295 if (pnode->sysctl_child == NULL) 296 return (-1); 297 298 name[namelen] = CTL_QUERY; 299 pnode->sysctl_clen = 0; 300 pnode->sysctl_csize = 0; 301 memset(&qnode, 0, sizeof(qnode)); 302 qnode.sysctl_flags = SYSCTL_VERSION; 303 rc = sysctl(name, namelen + 1, pnode->sysctl_child, &sz, 304 &qnode, sizeof(qnode)); 305 if (sz == 0) { 306 free(pnode->sysctl_child); 307 pnode->sysctl_child = NULL; 308 return (rc); 309 } 310 if (rc) { 311 free(pnode->sysctl_child); 312 pnode->sysctl_child = NULL; 313 if ((sz % sizeof(struct sysctlnode)) != 0) 314 errno = EINVAL; 315 if (errno != ENOMEM) 316 return (rc); 317 } 318 319 if (pnode->sysctl_child == NULL) { 320 pnode->sysctl_child = malloc(sz); 321 if (pnode->sysctl_child == NULL) 322 return (-1); 323 324 rc = sysctl(name, namelen + 1, pnode->sysctl_child, &sz, 325 &qnode, sizeof(qnode)); 326 if (rc) { 327 free(pnode->sysctl_child); 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 #ifdef _REENTRANT 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 */ 389 390 int 391 sysctlgetmibinfo(const char *gname, int *iname, u_int *namelenp, 392 char *cname, size_t *csz, struct sysctlnode **rnode, int v) 393 #ifdef _REENTRANT 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 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 */ 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 return (EINVAL); 426 427 pnode = &sysctl_mibroot; 428 } 429 else { 430 /* this is just someone being silly */ 431 if (SYSCTL_VERS((*rnode)->sysctl_flags) != (uint32_t)v) 432 return (EINVAL); 433 434 /* XXX later deal with other people's trees */ 435 if (SYSCTL_VERS((*rnode)->sysctl_flags) != 436 SYSCTL_VERSION) 437 return (EINVAL); 438 439 pnode = *rnode; 440 } 441 } 442 else 443 pnode = &sysctl_mibroot; 444 445 if (pnode == &sysctl_mibroot) 446 relearnhead(); 447 448 nl = ni = 0; 449 token[0] = '\0'; 450 pname[0] = '\0'; 451 node = NULL; 452 453 /* 454 * default to using '.' as the separator, but allow '/' as 455 * well, and then allow a leading separator 456 */ 457 if ((dot = strpbrk(gname, "./")) == NULL) 458 sep[0] = '.'; 459 else 460 sep[0] = dot[0]; 461 sep[1] = '\0'; 462 if (gname[0] == sep[0]) { 463 strlcat(pname, sep, sizeof(pname)); 464 gname++; 465 } 466 467 #define COPY_OUT_DATA(t, c, cs, nlp, l) do { \ 468 if ((c) != NULL && (cs) != NULL) \ 469 *(cs) = strlcpy((c), (t), *(cs)); \ 470 else if ((cs) != NULL) \ 471 *(cs) = strlen(t) + 1; \ 472 if ((nlp) != NULL) \ 473 *(nlp) = (l); \ 474 } while (/*CONSTCOND*/0) 475 476 piece = gname; 477 while (piece != NULL && *piece != '\0') { 478 /* 479 * what was i looking for? 480 */ 481 dot = strchr(piece, sep[0]); 482 if (dot == NULL) { 483 l = strlcpy(token, piece, sizeof(token)); 484 if (l > sizeof(token)) { 485 COPY_OUT_DATA(piece, cname, csz, namelenp, nl); 486 errno = ENAMETOOLONG; 487 return (-1); 488 } 489 } 490 else if (dot - piece > (intptr_t)(sizeof(token) - 1)) { 491 COPY_OUT_DATA(token, cname, csz, namelenp, nl); 492 errno = ENAMETOOLONG; 493 return (-1); 494 } 495 else { 496 strncpy(token, piece, (size_t)(dot - piece)); 497 token[dot - piece] = '\0'; 498 } 499 500 /* 501 * i wonder if this "token" is an integer? 502 */ 503 errno = 0; 504 q = strtoimax(token, &t, 0); 505 n = (int)q; 506 if (errno != 0 || *t != '\0') 507 haven = 0; 508 else if (q < INT_MIN || q > UINT_MAX) 509 haven = 0; 510 else 511 haven = 1; 512 513 /* 514 * make sure i have something to look at 515 */ 516 if (SYSCTL_TYPE(pnode->sysctl_flags) != CTLTYPE_NODE) { 517 if (haven && nl > 0) { 518 strlcat(pname, sep, sizeof(pname)); 519 goto just_numbers; 520 } 521 COPY_OUT_DATA(token, cname, csz, namelenp, nl); 522 errno = ENOTDIR; 523 return (-1); 524 } 525 if (pnode->sysctl_child == NULL) { 526 if (__learn_tree(name, nl, pnode) == -1) { 527 COPY_OUT_DATA(token, cname, csz, namelenp, nl); 528 return (-1); 529 } 530 } 531 node = pnode->sysctl_child; 532 if (node == NULL) { 533 COPY_OUT_DATA(token, cname, csz, namelenp, nl); 534 errno = ENOENT; 535 return (-1); 536 } 537 538 /* 539 * now...is it there? 540 */ 541 for (ni = 0; ni < pnode->sysctl_clen; ni++) 542 if ((haven && ((n == node[ni].sysctl_num) || 543 (node[ni].sysctl_flags & CTLFLAG_ANYNUMBER))) || 544 strcmp(token, node[ni].sysctl_name) == 0) 545 break; 546 if (ni >= pnode->sysctl_clen) { 547 COPY_OUT_DATA(token, cname, csz, namelenp, nl); 548 errno = ENOENT; 549 return (-1); 550 } 551 552 /* 553 * ah...it is. 554 */ 555 pnode = &node[ni]; 556 if (nl > 0) 557 strlcat(pname, sep, sizeof(pname)); 558 if (haven && n != pnode->sysctl_num) { 559 just_numbers: 560 strlcat(pname, token, sizeof(pname)); 561 name[nl] = n; 562 } 563 else { 564 strlcat(pname, pnode->sysctl_name, sizeof(pname)); 565 name[nl] = pnode->sysctl_num; 566 } 567 piece = (dot != NULL) ? dot + 1 : NULL; 568 nl++; 569 if (nl == CTL_MAXNAME) { 570 COPY_OUT_DATA(token, cname, csz, namelenp, nl); 571 errno = ERANGE; 572 return (-1); 573 } 574 } 575 576 if (nl == 0) { 577 if (namelenp != NULL) 578 *namelenp = 0; 579 errno = EINVAL; 580 return (-1); 581 } 582 583 COPY_OUT_DATA(pname, cname, csz, namelenp, nl); 584 if (iname != NULL && namelenp != NULL) 585 memcpy(iname, &name[0], MIN(nl, *namelenp) * sizeof(int)); 586 if (namelenp != NULL) 587 *namelenp = nl; 588 if (rnode != NULL) { 589 if (*rnode != NULL) 590 /* 591 * they gave us a private tree to work in, so 592 * we give back a pointer into that private 593 * tree 594 */ 595 *rnode = pnode; 596 else { 597 /* 598 * they gave us a place to put the node data, 599 * so give them a copy 600 */ 601 *rnode = malloc(sizeof(struct sysctlnode)); 602 if (*rnode != NULL) { 603 **rnode = *pnode; 604 (*rnode)->sysctl_child = NULL; 605 (*rnode)->sysctl_parent = NULL; 606 } 607 } 608 } 609 610 return (0); 611 } 612