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