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