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