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