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