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