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