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