1 /*- 2 * Copyright (c) 2005-2006 The FreeBSD Project 3 * All rights reserved. 4 * 5 * Author: Victor Cruceru <soc-victor@freebsd.org> 6 * 7 * Redistribution of this software and documentation and use in source and 8 * binary forms, with or without modification, are permitted provided that 9 * the following conditions are met: 10 * 11 * 1. Redistributions of source code or documentation must retain the above 12 * copyright notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 * $FreeBSD$ 30 */ 31 32 /* 33 * Host Resources MIB: hrDeviceTable implementation for SNMPd. 34 */ 35 36 #include <sys/un.h> 37 #include <sys/limits.h> 38 39 #include <assert.h> 40 #include <err.h> 41 #include <errno.h> 42 #include <stdlib.h> 43 #include <string.h> 44 #include <syslog.h> 45 #include <unistd.h> 46 #include <sysexits.h> 47 48 #include "hostres_snmp.h" 49 #include "hostres_oid.h" 50 #include "hostres_tree.h" 51 52 #define FREE_DEV_STRUCT(entry_p) do { \ 53 free(entry_p->name); \ 54 free(entry_p->location); \ 55 free(entry_p->descr); \ 56 free(entry_p); \ 57 } while (0) 58 59 /* 60 * Status of a device 61 */ 62 enum DeviceStatus { 63 DS_UNKNOWN = 1, 64 DS_RUNNING = 2, 65 DS_WARNING = 3, 66 DS_TESTING = 4, 67 DS_DOWN = 5 68 }; 69 70 TAILQ_HEAD(device_tbl, device_entry); 71 72 /* the head of the list with hrDeviceTable's entries */ 73 static struct device_tbl device_tbl = TAILQ_HEAD_INITIALIZER(device_tbl); 74 75 /* Table used for consistent device table indexing. */ 76 struct device_map device_map = STAILQ_HEAD_INITIALIZER(device_map); 77 78 /* next int available for indexing the hrDeviceTable */ 79 static uint32_t next_device_index = 1; 80 81 /* last (agent) tick when hrDeviceTable was updated */ 82 static uint64_t device_tick = 0; 83 84 /* maximum number of ticks between updates of device table */ 85 uint32_t device_tbl_refresh = 10 * 100; 86 87 /* socket for /var/run/devd.pipe */ 88 static int devd_sock = -1; 89 90 /* used to wait notifications from /var/run/devd.pipe */ 91 static void *devd_fd; 92 93 /* some constants */ 94 static const struct asn_oid OIDX_hrDeviceProcessor_c = OIDX_hrDeviceProcessor; 95 static const struct asn_oid OIDX_hrDeviceOther_c = OIDX_hrDeviceOther; 96 97 /** 98 * Create a new entry out of thin air. 99 */ 100 struct device_entry * 101 device_entry_create(const char *name, const char *location, const char *descr) 102 { 103 struct device_entry *entry = NULL; 104 struct device_map_entry *map = NULL; 105 size_t name_len; 106 size_t location_len; 107 108 assert((name[0] != 0) || (location[0] != 0)); 109 110 if (name[0] == 0 && location[0] == 0) 111 return (NULL); 112 113 STAILQ_FOREACH(map, &device_map, link) { 114 assert(map->name_key != NULL); 115 assert(map->location_key != NULL); 116 117 if (strcmp(map->name_key, name) == 0 && 118 strcmp(map->location_key, location) == 0) { 119 break; 120 } 121 } 122 123 if (map == NULL) { 124 /* new object - get a new index */ 125 if (next_device_index > INT_MAX) { 126 syslog(LOG_ERR, 127 "%s: hrDeviceTable index wrap", __func__); 128 /* There isn't much we can do here. 129 * If the next_swins_index is consumed 130 * then we can't add entries to this table 131 * So it is better to exit - if the table is sparsed 132 * at the next agent run we can fill it fully. 133 */ 134 errx(EX_SOFTWARE, "hrDeviceTable index wrap"); 135 /* not reachable */ 136 } 137 138 if ((map = malloc(sizeof(*map))) == NULL) { 139 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); 140 return (NULL); 141 } 142 143 map->entry_p = NULL; 144 145 name_len = strlen(name) + 1; 146 if (name_len > DEV_NAME_MLEN) 147 name_len = DEV_NAME_MLEN; 148 149 if ((map->name_key = malloc(name_len)) == NULL) { 150 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); 151 free(map); 152 return (NULL); 153 } 154 155 location_len = strlen(location) + 1; 156 if (location_len > DEV_LOC_MLEN) 157 location_len = DEV_LOC_MLEN; 158 159 if ((map->location_key = malloc(location_len )) == NULL) { 160 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); 161 free(map->name_key); 162 free(map); 163 return (NULL); 164 } 165 166 map->hrIndex = next_device_index++; 167 168 strlcpy(map->name_key, name, name_len); 169 strlcpy(map->location_key, location, location_len); 170 171 STAILQ_INSERT_TAIL(&device_map, map, link); 172 HRDBG("%s at %s added into hrDeviceMap at index=%d", 173 name, location, map->hrIndex); 174 } else { 175 HRDBG("%s at %s exists in hrDeviceMap index=%d", 176 name, location, map->hrIndex); 177 } 178 179 if ((entry = malloc(sizeof(*entry))) == NULL) { 180 syslog(LOG_WARNING, "hrDeviceTable: %s: %m", __func__); 181 return (NULL); 182 } 183 memset(entry, 0, sizeof(*entry)); 184 185 entry->index = map->hrIndex; 186 map->entry_p = entry; 187 188 if ((entry->name = strdup(map->name_key)) == NULL) { 189 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); 190 free(entry); 191 return (NULL); 192 } 193 194 if ((entry->location = strdup(map->location_key)) == NULL) { 195 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); 196 free(entry->name); 197 free(entry); 198 return (NULL); 199 } 200 201 /* 202 * From here till the end of this function we reuse name_len 203 * for a diferrent purpose - for device_entry::descr 204 */ 205 if (name[0] != '\0') 206 name_len = strlen(name) + strlen(descr) + 207 strlen(": ") + 1; 208 else 209 name_len = strlen(location) + strlen(descr) + 210 strlen("unknown at : ") + 1; 211 212 if (name_len > DEV_DESCR_MLEN) 213 name_len = DEV_DESCR_MLEN; 214 215 if ((entry->descr = malloc(name_len )) == NULL) { 216 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); 217 free(entry->name); 218 free(entry->location); 219 free(entry); 220 return (NULL); 221 } 222 223 memset(&entry->descr[0], '\0', name_len); 224 225 if (name[0] != '\0') 226 snprintf(entry->descr, name_len, 227 "%s: %s", name, descr); 228 else 229 snprintf(entry->descr, name_len, 230 "unknown at %s: %s", location, descr); 231 232 entry->id = &oid_zeroDotZero; /* unknown id - FIXME */ 233 entry->status = (u_int)DS_UNKNOWN; 234 entry->errors = 0; 235 entry->type = &OIDX_hrDeviceOther_c; 236 237 INSERT_OBJECT_INT(entry, &device_tbl); 238 239 return (entry); 240 } 241 242 /** 243 * Create a new entry into the device table. 244 */ 245 static struct device_entry * 246 device_entry_create_devinfo(const struct devinfo_dev *dev_p) 247 { 248 249 assert(dev_p->dd_name != NULL); 250 assert(dev_p->dd_location != NULL); 251 252 return (device_entry_create(dev_p->dd_name, dev_p->dd_location, 253 dev_p->dd_desc)); 254 } 255 256 /** 257 * Delete an entry from the device table. 258 */ 259 void 260 device_entry_delete(struct device_entry *entry) 261 { 262 struct device_map_entry *map; 263 264 assert(entry != NULL); 265 266 TAILQ_REMOVE(&device_tbl, entry, link); 267 268 STAILQ_FOREACH(map, &device_map, link) 269 if (map->entry_p == entry) { 270 map->entry_p = NULL; 271 break; 272 } 273 274 FREE_DEV_STRUCT(entry); 275 } 276 277 /** 278 * Find an entry given its name and location 279 */ 280 static struct device_entry * 281 device_find_by_dev(const struct devinfo_dev *dev_p) 282 { 283 struct device_map_entry *map; 284 285 assert(dev_p != NULL); 286 287 STAILQ_FOREACH(map, &device_map, link) 288 if (strcmp(map->name_key, dev_p->dd_name) == 0 && 289 strcmp(map->location_key, dev_p->dd_location) == 0) 290 return (map->entry_p); 291 return (NULL); 292 } 293 294 /** 295 * Find an entry given its index. 296 */ 297 struct device_entry * 298 device_find_by_index(int32_t idx) 299 { 300 struct device_entry *entry; 301 302 TAILQ_FOREACH(entry, &device_tbl, link) 303 if (entry->index == idx) 304 return (entry); 305 return (NULL); 306 } 307 308 /** 309 * Find an device entry given its name. 310 */ 311 struct device_entry * 312 device_find_by_name(const char *dev_name) 313 { 314 struct device_map_entry *map; 315 316 assert(dev_name != NULL); 317 318 STAILQ_FOREACH(map, &device_map, link) 319 if (strcmp(map->name_key, dev_name) == 0) 320 return (map->entry_p); 321 322 return (NULL); 323 } 324 325 /** 326 * Find out the type of device. CPU only currently. 327 */ 328 static void 329 device_get_type(struct devinfo_dev *dev_p, const struct asn_oid **out_type_p) 330 { 331 332 assert(dev_p != NULL); 333 assert(out_type_p != NULL); 334 335 if (dev_p == NULL) 336 return; 337 338 if (strncmp(dev_p->dd_name, "cpu", strlen("cpu")) == 0 && 339 strstr(dev_p->dd_location, ".CPU") != NULL) { 340 *out_type_p = &OIDX_hrDeviceProcessor_c; 341 return; 342 } 343 } 344 345 /** 346 * Get the status of a device 347 */ 348 static enum DeviceStatus 349 device_get_status(struct devinfo_dev *dev) 350 { 351 352 assert(dev != NULL); 353 354 switch (dev->dd_state) { 355 case DS_ALIVE: /* probe succeeded */ 356 case DS_NOTPRESENT: /* not probed or probe failed */ 357 return (DS_DOWN); 358 case DS_ATTACHED: /* attach method called */ 359 case DS_BUSY: /* device is open */ 360 return (DS_RUNNING); 361 default: 362 return (DS_UNKNOWN); 363 } 364 } 365 366 /** 367 * Get the info for the given device and then recursively process all 368 * child devices. 369 */ 370 static int 371 device_collector(struct devinfo_dev *dev, void *arg) 372 { 373 struct device_entry *entry; 374 375 HRDBG("%llu/%llu name='%s' desc='%s' drivername='%s' location='%s'", 376 (unsigned long long)dev->dd_handle, 377 (unsigned long long)dev->dd_parent, dev->dd_name, dev->dd_desc, 378 dev->dd_drivername, dev->dd_location); 379 380 if (dev->dd_name[0] != '\0' || dev->dd_location[0] != '\0') { 381 HRDBG("ANALYZING dev %s at %s", 382 dev->dd_name, dev->dd_location); 383 384 if ((entry = device_find_by_dev(dev)) != NULL) { 385 entry->flags |= HR_DEVICE_FOUND; 386 entry->status = (u_int)device_get_status(dev); 387 } else if ((entry = device_entry_create_devinfo(dev)) != NULL) { 388 device_get_type(dev, &entry->type); 389 390 entry->flags |= HR_DEVICE_FOUND; 391 entry->status = (u_int)device_get_status(dev); 392 } 393 } else { 394 HRDBG("SKIPPED unknown device at location '%s'", 395 dev->dd_location ); 396 } 397 398 return (devinfo_foreach_device_child(dev, device_collector, arg)); 399 } 400 401 /** 402 * Create the socket to the device daemon. 403 */ 404 static int 405 create_devd_socket(void) 406 { 407 int d_sock; 408 struct sockaddr_un devd_addr; 409 410 bzero(&devd_addr, sizeof(struct sockaddr_un)); 411 412 if ((d_sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) { 413 syslog(LOG_ERR, "Failed to create the socket for %s: %m", 414 PATH_DEVD_PIPE); 415 return (-1); 416 } 417 418 devd_addr.sun_family = PF_LOCAL; 419 devd_addr.sun_len = sizeof(devd_addr); 420 strlcpy(devd_addr.sun_path, PATH_DEVD_PIPE, 421 sizeof(devd_addr.sun_path) - 1); 422 423 if (connect(d_sock, (struct sockaddr *)&devd_addr, 424 sizeof(devd_addr)) == -1) { 425 syslog(LOG_ERR,"Failed to connect socket for %s: %m", 426 PATH_DEVD_PIPE); 427 if (close(d_sock) < 0 ) 428 syslog(LOG_ERR,"Failed to close socket for %s: %m", 429 PATH_DEVD_PIPE); 430 return (-1); 431 } 432 433 return (d_sock); 434 } 435 436 /* 437 * Event on the devd socket. 438 * 439 * We should probably directly process entries here. For simplicity just 440 * call the refresh routine with the force flag for now. 441 */ 442 static void 443 devd_socket_callback(int fd, void *arg __unused) 444 { 445 char buf[512]; 446 int read_len = -1; 447 448 assert(fd == devd_sock); 449 450 HRDBG("called"); 451 452 read_len = read(fd, buf, sizeof(buf) - 1); 453 if (read_len < 0) { 454 if (errno == EBADF) { 455 devd_sock = -1; 456 if (devd_fd != NULL) { 457 fd_deselect(devd_fd); 458 devd_fd = NULL; 459 } 460 syslog(LOG_ERR, "Closing devd_fd, revert to " 461 "devinfo polling"); 462 } 463 464 } else if (read_len == 0) { 465 syslog(LOG_ERR, "zero bytes read from devd pipe... " 466 "closing socket!"); 467 468 if (close(devd_sock) < 0 ) 469 syslog(LOG_ERR, "Failed to close devd socket: %m"); 470 471 devd_sock = -1; 472 if (devd_fd != NULL) { 473 fd_deselect(devd_fd); 474 devd_fd = NULL; 475 } 476 syslog(LOG_ERR, "Closing devd_fd, revert to devinfo polling"); 477 478 } else { 479 switch (buf[0]) { 480 case '+': 481 case '-': 482 case '?': 483 case '!': 484 refresh_device_tbl(1); 485 return; 486 default: 487 syslog(LOG_ERR, "unknown message from devd socket"); 488 } 489 } 490 } 491 492 /** 493 * Initialize and populate the device table. 494 */ 495 void 496 init_device_tbl(void) 497 { 498 499 /* initially populate table for the other tables */ 500 refresh_device_tbl(1); 501 502 /* no problem if that fails - just use polling mode */ 503 devd_sock = create_devd_socket(); 504 } 505 506 /** 507 * Start devd(8) monitoring. 508 */ 509 void 510 start_device_tbl(struct lmodule *mod) 511 { 512 513 if (devd_sock > 0) { 514 devd_fd = fd_select(devd_sock, devd_socket_callback, NULL, mod); 515 if (devd_fd == NULL) 516 syslog(LOG_ERR, "fd_select failed on devd socket: %m"); 517 } 518 } 519 520 /** 521 * Finalization routine for hrDeviceTable 522 * It destroys the lists and frees any allocated heap memory 523 */ 524 void 525 fini_device_tbl(void) 526 { 527 struct device_map_entry *n1; 528 529 if (devd_fd != NULL) 530 fd_deselect(devd_fd); 531 532 if (devd_sock != -1) 533 (void)close(devd_sock); 534 535 devinfo_free(); 536 537 while ((n1 = STAILQ_FIRST(&device_map)) != NULL) { 538 STAILQ_REMOVE_HEAD(&device_map, link); 539 if (n1->entry_p != NULL) { 540 TAILQ_REMOVE(&device_tbl, n1->entry_p, link); 541 FREE_DEV_STRUCT(n1->entry_p); 542 } 543 free(n1->name_key); 544 free(n1->location_key); 545 free(n1); 546 } 547 assert(TAILQ_EMPTY(&device_tbl)); 548 } 549 550 /** 551 * Refresh routine for hrDeviceTable. We don't refresh here if the devd socket 552 * is open, because in this case we have the actual information always. We 553 * also don't refresh when the table is new enough (if we don't have a devd 554 * socket). In either case a refresh can be forced by passing a non-zero value. 555 */ 556 void 557 refresh_device_tbl(int force) 558 { 559 struct device_entry *entry, *entry_tmp; 560 struct devinfo_dev *dev_root; 561 static int act = 0; 562 563 if (!force && (devd_sock >= 0 || 564 (device_tick != 0 && this_tick - device_tick < device_tbl_refresh))){ 565 HRDBG("no refresh needed"); 566 return; 567 } 568 569 if (act) { 570 syslog(LOG_ERR, "%s: recursive call", __func__); 571 return; 572 } 573 574 if (devinfo_init() != 0) { 575 syslog(LOG_ERR,"%s: devinfo_init failed: %m", __func__); 576 return; 577 } 578 579 act = 1; 580 if ((dev_root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL){ 581 syslog(LOG_ERR, "%s: can't get the root device: %m", __func__); 582 goto out; 583 } 584 585 /* mark each entry as missing */ 586 TAILQ_FOREACH(entry, &device_tbl, link) 587 entry->flags &= ~HR_DEVICE_FOUND; 588 589 if (devinfo_foreach_device_child(dev_root, device_collector, NULL)) 590 syslog(LOG_ERR, "%s: devinfo_foreach_device_child failed", 591 __func__); 592 593 /* 594 * Purge items that disappeared 595 */ 596 TAILQ_FOREACH_SAFE(entry, &device_tbl, link, entry_tmp) { 597 /* 598 * If HR_DEVICE_IMMUTABLE bit is set then this means that 599 * this entry was not detected by the above 600 * devinfo_foreach_device() call. So we are not deleting 601 * it there. 602 */ 603 if (!(entry->flags & HR_DEVICE_FOUND) && 604 !(entry->flags & HR_DEVICE_IMMUTABLE)) 605 device_entry_delete(entry); 606 } 607 608 device_tick = this_tick; 609 610 /* 611 * Force a refresh for the hrDiskStorageTable 612 * XXX Why not the other dependen tables? 613 */ 614 refresh_disk_storage_tbl(1); 615 616 out: 617 devinfo_free(); 618 act = 0; 619 } 620 621 /** 622 * This is the implementation for a generated (by a SNMP tool) 623 * function prototype, see hostres_tree.h 624 * It handles the SNMP operations for hrDeviceTable 625 */ 626 int 627 op_hrDeviceTable(struct snmp_context *ctx __unused, struct snmp_value *value, 628 u_int sub, u_int iidx __unused, enum snmp_op curr_op) 629 { 630 struct device_entry *entry; 631 632 refresh_device_tbl(0); 633 634 switch (curr_op) { 635 636 case SNMP_OP_GETNEXT: 637 if ((entry = NEXT_OBJECT_INT(&device_tbl, 638 &value->var, sub)) == NULL) 639 return (SNMP_ERR_NOSUCHNAME); 640 value->var.len = sub + 1; 641 value->var.subs[sub] = entry->index; 642 goto get; 643 644 case SNMP_OP_GET: 645 if ((entry = FIND_OBJECT_INT(&device_tbl, 646 &value->var, sub)) == NULL) 647 return (SNMP_ERR_NOSUCHNAME); 648 goto get; 649 650 case SNMP_OP_SET: 651 if ((entry = FIND_OBJECT_INT(&device_tbl, 652 &value->var, sub)) == NULL) 653 return (SNMP_ERR_NO_CREATION); 654 return (SNMP_ERR_NOT_WRITEABLE); 655 656 case SNMP_OP_ROLLBACK: 657 case SNMP_OP_COMMIT: 658 abort(); 659 } 660 abort(); 661 662 get: 663 switch (value->var.subs[sub - 1]) { 664 665 case LEAF_hrDeviceIndex: 666 value->v.integer = entry->index; 667 return (SNMP_ERR_NOERROR); 668 669 case LEAF_hrDeviceType: 670 assert(entry->type != NULL); 671 value->v.oid = *(entry->type); 672 return (SNMP_ERR_NOERROR); 673 674 case LEAF_hrDeviceDescr: 675 return (string_get(value, entry->descr, -1)); 676 677 case LEAF_hrDeviceID: 678 value->v.oid = *(entry->id); 679 return (SNMP_ERR_NOERROR); 680 681 case LEAF_hrDeviceStatus: 682 value->v.integer = entry->status; 683 return (SNMP_ERR_NOERROR); 684 685 case LEAF_hrDeviceErrors: 686 value->v.uint32 = entry->errors; 687 return (SNMP_ERR_NOERROR); 688 } 689 abort(); 690 } 691