diff options
Diffstat (limited to 'usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c')
-rw-r--r-- | usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c | 684 |
1 files changed, 684 insertions, 0 deletions
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c new file mode 100644 index 0000000..1741502 --- /dev/null +++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c @@ -0,0 +1,684 @@ + /*- + * Copyright (c) 2005-2006 The FreeBSD Project + * All rights reserved. + * + * Author: Victor Cruceru <soc-victor@freebsd.org> + * + * Redistribution of this software and documentation and use in source and + * binary forms, with or without modification, are permitted provided that + * the following conditions are met: + * + * 1. Redistributions of source code or documentation must retain the above + * copyright notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Host Resources MIB: hrDeviceTable implementation for SNMPd. + */ + +#include <sys/un.h> +#include <sys/limits.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> +#include <sysexits.h> + +#include "hostres_snmp.h" +#include "hostres_oid.h" +#include "hostres_tree.h" + +#define FREE_DEV_STRUCT(entry_p) do { \ + free(entry_p->name); \ + free(entry_p->location); \ + free(entry_p->descr); \ + free(entry_p); \ +} while (0) + +/* + * Status of a device + */ +enum DeviceStatus { + DS_UNKNOWN = 1, + DS_RUNNING = 2, + DS_WARNING = 3, + DS_TESTING = 4, + DS_DOWN = 5 +}; + +TAILQ_HEAD(device_tbl, device_entry); + +/* the head of the list with hrDeviceTable's entries */ +static struct device_tbl device_tbl = TAILQ_HEAD_INITIALIZER(device_tbl); + +/* Table used for consistent device table indexing. */ +struct device_map device_map = STAILQ_HEAD_INITIALIZER(device_map); + +/* next int available for indexing the hrDeviceTable */ +static uint32_t next_device_index = 1; + +/* last (agent) tick when hrDeviceTable was updated */ +static uint64_t device_tick = 0; + +/* maximum number of ticks between updates of device table */ +uint32_t device_tbl_refresh = 10 * 100; + +/* socket for /var/run/devd.pipe */ +static int devd_sock = -1; + +/* used to wait notifications from /var/run/devd.pipe */ +static void *devd_fd; + +/* some constants */ +static const struct asn_oid OIDX_hrDeviceProcessor_c = OIDX_hrDeviceProcessor; +static const struct asn_oid OIDX_hrDeviceOther_c = OIDX_hrDeviceOther; + +/** + * Create a new entry out of thin air. + */ +struct device_entry * +device_entry_create(const char *name, const char *location, const char *descr) +{ + struct device_entry *entry = NULL; + struct device_map_entry *map = NULL; + size_t name_len; + size_t location_len; + + assert((name[0] != 0) || (location[0] != 0)); + + if (name[0] == 0 && location[0] == 0) + return (NULL); + + STAILQ_FOREACH(map, &device_map, link) { + assert(map->name_key != NULL); + assert(map->location_key != NULL); + + if (strcmp(map->name_key, name) == 0 && + strcmp(map->location_key, location) == 0) { + break; + } + } + + if (map == NULL) { + /* new object - get a new index */ + if (next_device_index > INT_MAX) { + syslog(LOG_ERR, + "%s: hrDeviceTable index wrap", __func__); + /* There isn't much we can do here. + * If the next_swins_index is consumed + * then we can't add entries to this table + * So it is better to exit - if the table is sparsed + * at the next agent run we can fill it fully. + */ + errx(EX_SOFTWARE, "hrDeviceTable index wrap"); + /* not reachable */ + } + + if ((map = malloc(sizeof(*map))) == NULL) { + syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); + return (NULL); + } + + map->entry_p = NULL; + + name_len = strlen(name) + 1; + if (name_len > DEV_NAME_MLEN) + name_len = DEV_NAME_MLEN; + + if ((map->name_key = malloc(name_len)) == NULL) { + syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); + free(map); + return (NULL); + } + + location_len = strlen(location) + 1; + if (location_len > DEV_LOC_MLEN) + location_len = DEV_LOC_MLEN; + + if ((map->location_key = malloc(location_len )) == NULL) { + syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); + free(map->name_key); + free(map); + return (NULL); + } + + map->hrIndex = next_device_index++; + + strlcpy(map->name_key, name, name_len); + strlcpy(map->location_key, location, location_len); + + STAILQ_INSERT_TAIL(&device_map, map, link); + HRDBG("%s at %s added into hrDeviceMap at index=%d", + name, location, map->hrIndex); + } else { + HRDBG("%s at %s exists in hrDeviceMap index=%d", + name, location, map->hrIndex); + } + + if ((entry = malloc(sizeof(*entry))) == NULL) { + syslog(LOG_WARNING, "hrDeviceTable: %s: %m", __func__); + return (NULL); + } + memset(entry, 0, sizeof(*entry)); + + entry->index = map->hrIndex; + map->entry_p = entry; + + if ((entry->name = strdup(map->name_key)) == NULL) { + syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); + free(entry); + return (NULL); + } + + if ((entry->location = strdup(map->location_key)) == NULL) { + syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); + free(entry->name); + free(entry); + return (NULL); + } + + /* + * From here till the end of this function we reuse name_len + * for a different purpose - for device_entry::descr + */ + if (name[0] != '\0') + name_len = strlen(name) + strlen(descr) + + strlen(": ") + 1; + else + name_len = strlen(location) + strlen(descr) + + strlen("unknown at : ") + 1; + + if (name_len > DEV_DESCR_MLEN) + name_len = DEV_DESCR_MLEN; + + if ((entry->descr = malloc(name_len )) == NULL) { + syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ ); + free(entry->name); + free(entry->location); + free(entry); + return (NULL); + } + + memset(&entry->descr[0], '\0', name_len); + + if (name[0] != '\0') + snprintf(entry->descr, name_len, + "%s: %s", name, descr); + else + snprintf(entry->descr, name_len, + "unknown at %s: %s", location, descr); + + entry->id = &oid_zeroDotZero; /* unknown id - FIXME */ + entry->status = (u_int)DS_UNKNOWN; + entry->errors = 0; + entry->type = &OIDX_hrDeviceOther_c; + + INSERT_OBJECT_INT(entry, &device_tbl); + + return (entry); +} + +/** + * Create a new entry into the device table. + */ +static struct device_entry * +device_entry_create_devinfo(const struct devinfo_dev *dev_p) +{ + + assert(dev_p->dd_name != NULL); + assert(dev_p->dd_location != NULL); + + return (device_entry_create(dev_p->dd_name, dev_p->dd_location, + dev_p->dd_desc)); +} + +/** + * Delete an entry from the device table. + */ +void +device_entry_delete(struct device_entry *entry) +{ + struct device_map_entry *map; + + assert(entry != NULL); + + TAILQ_REMOVE(&device_tbl, entry, link); + + STAILQ_FOREACH(map, &device_map, link) + if (map->entry_p == entry) { + map->entry_p = NULL; + break; + } + + FREE_DEV_STRUCT(entry); +} + +/** + * Find an entry given its name and location + */ +static struct device_entry * +device_find_by_dev(const struct devinfo_dev *dev_p) +{ + struct device_map_entry *map; + + assert(dev_p != NULL); + + STAILQ_FOREACH(map, &device_map, link) + if (strcmp(map->name_key, dev_p->dd_name) == 0 && + strcmp(map->location_key, dev_p->dd_location) == 0) + return (map->entry_p); + return (NULL); +} + +/** + * Find an entry given its index. + */ +struct device_entry * +device_find_by_index(int32_t idx) +{ + struct device_entry *entry; + + TAILQ_FOREACH(entry, &device_tbl, link) + if (entry->index == idx) + return (entry); + return (NULL); +} + +/** + * Find an device entry given its name. + */ +struct device_entry * +device_find_by_name(const char *dev_name) +{ + struct device_map_entry *map; + + assert(dev_name != NULL); + + STAILQ_FOREACH(map, &device_map, link) + if (strcmp(map->name_key, dev_name) == 0) + return (map->entry_p); + + return (NULL); +} + +/** + * Find out the type of device. CPU only currently. + */ +static void +device_get_type(struct devinfo_dev *dev_p, const struct asn_oid **out_type_p) +{ + + assert(dev_p != NULL); + assert(out_type_p != NULL); + + if (dev_p == NULL) + return; + + if (strncmp(dev_p->dd_name, "cpu", strlen("cpu")) == 0 && + strstr(dev_p->dd_location, ".CPU") != NULL) { + *out_type_p = &OIDX_hrDeviceProcessor_c; + return; + } +} + +/** + * Get the status of a device + */ +static enum DeviceStatus +device_get_status(struct devinfo_dev *dev) +{ + + assert(dev != NULL); + + switch (dev->dd_state) { + case DS_ALIVE: /* probe succeeded */ + case DS_NOTPRESENT: /* not probed or probe failed */ + return (DS_DOWN); + case DS_ATTACHED: /* attach method called */ + case DS_BUSY: /* device is open */ + return (DS_RUNNING); + default: + return (DS_UNKNOWN); + } +} + +/** + * Get the info for the given device and then recursively process all + * child devices. + */ +static int +device_collector(struct devinfo_dev *dev, void *arg) +{ + struct device_entry *entry; + + HRDBG("%llu/%llu name='%s' desc='%s' drivername='%s' location='%s'", + (unsigned long long)dev->dd_handle, + (unsigned long long)dev->dd_parent, dev->dd_name, dev->dd_desc, + dev->dd_drivername, dev->dd_location); + + if (dev->dd_name[0] != '\0' || dev->dd_location[0] != '\0') { + HRDBG("ANALYZING dev %s at %s", + dev->dd_name, dev->dd_location); + + if ((entry = device_find_by_dev(dev)) != NULL) { + entry->flags |= HR_DEVICE_FOUND; + entry->status = (u_int)device_get_status(dev); + } else if ((entry = device_entry_create_devinfo(dev)) != NULL) { + device_get_type(dev, &entry->type); + + entry->flags |= HR_DEVICE_FOUND; + entry->status = (u_int)device_get_status(dev); + } + } else { + HRDBG("SKIPPED unknown device at location '%s'", + dev->dd_location ); + } + + return (devinfo_foreach_device_child(dev, device_collector, arg)); +} + +/** + * Create the socket to the device daemon. + */ +static int +create_devd_socket(void) +{ + int d_sock; + struct sockaddr_un devd_addr; + + bzero(&devd_addr, sizeof(struct sockaddr_un)); + + if ((d_sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) { + syslog(LOG_ERR, "Failed to create the socket for %s: %m", + PATH_DEVD_PIPE); + return (-1); + } + + devd_addr.sun_family = PF_LOCAL; + devd_addr.sun_len = sizeof(devd_addr); + strlcpy(devd_addr.sun_path, PATH_DEVD_PIPE, + sizeof(devd_addr.sun_path) - 1); + + if (connect(d_sock, (struct sockaddr *)&devd_addr, + sizeof(devd_addr)) == -1) { + syslog(LOG_ERR,"Failed to connect socket for %s: %m", + PATH_DEVD_PIPE); + if (close(d_sock) < 0 ) + syslog(LOG_ERR,"Failed to close socket for %s: %m", + PATH_DEVD_PIPE); + return (-1); + } + + return (d_sock); +} + +/* + * Event on the devd socket. + * + * We should probably directly process entries here. For simplicity just + * call the refresh routine with the force flag for now. + */ +static void +devd_socket_callback(int fd, void *arg __unused) +{ + char buf[512]; + int read_len = -1; + + assert(fd == devd_sock); + + HRDBG("called"); + +again: + read_len = read(fd, buf, sizeof(buf)); + if (read_len < 0) { + if (errno == EBADF) { + devd_sock = -1; + if (devd_fd != NULL) { + fd_deselect(devd_fd); + devd_fd = NULL; + } + syslog(LOG_ERR, "Closing devd_fd, revert to " + "devinfo polling"); + } + + } else if (read_len == 0) { + syslog(LOG_ERR, "zero bytes read from devd pipe... " + "closing socket!"); + + if (close(devd_sock) < 0 ) + syslog(LOG_ERR, "Failed to close devd socket: %m"); + + devd_sock = -1; + if (devd_fd != NULL) { + fd_deselect(devd_fd); + devd_fd = NULL; + } + syslog(LOG_ERR, "Closing devd_fd, revert to devinfo polling"); + + } else { + if (read_len == sizeof(buf)) + goto again; + refresh_device_tbl(1); + } +} + +/** + * Initialize and populate the device table. + */ +void +init_device_tbl(void) +{ + + /* initially populate table for the other tables */ + refresh_device_tbl(1); + + /* no problem if that fails - just use polling mode */ + devd_sock = create_devd_socket(); +} + +/** + * Start devd(8) monitoring. + */ +void +start_device_tbl(struct lmodule *mod) +{ + + if (devd_sock > 0) { + devd_fd = fd_select(devd_sock, devd_socket_callback, NULL, mod); + if (devd_fd == NULL) + syslog(LOG_ERR, "fd_select failed on devd socket: %m"); + } +} + +/** + * Finalization routine for hrDeviceTable + * It destroys the lists and frees any allocated heap memory + */ +void +fini_device_tbl(void) +{ + struct device_map_entry *n1; + + if (devd_fd != NULL) + fd_deselect(devd_fd); + + if (devd_sock != -1) + (void)close(devd_sock); + + devinfo_free(); + + while ((n1 = STAILQ_FIRST(&device_map)) != NULL) { + STAILQ_REMOVE_HEAD(&device_map, link); + if (n1->entry_p != NULL) { + TAILQ_REMOVE(&device_tbl, n1->entry_p, link); + FREE_DEV_STRUCT(n1->entry_p); + } + free(n1->name_key); + free(n1->location_key); + free(n1); + } + assert(TAILQ_EMPTY(&device_tbl)); +} + +/** + * Refresh routine for hrDeviceTable. We don't refresh here if the devd socket + * is open, because in this case we have the actual information always. We + * also don't refresh when the table is new enough (if we don't have a devd + * socket). In either case a refresh can be forced by passing a non-zero value. + */ +void +refresh_device_tbl(int force) +{ + struct device_entry *entry, *entry_tmp; + struct devinfo_dev *dev_root; + static int act = 0; + + if (!force && (devd_sock >= 0 || + (device_tick != 0 && this_tick - device_tick < device_tbl_refresh))){ + HRDBG("no refresh needed"); + return; + } + + if (act) { + syslog(LOG_ERR, "%s: recursive call", __func__); + return; + } + + if (devinfo_init() != 0) { + syslog(LOG_ERR,"%s: devinfo_init failed: %m", __func__); + return; + } + + act = 1; + if ((dev_root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL){ + syslog(LOG_ERR, "%s: can't get the root device: %m", __func__); + goto out; + } + + /* mark each entry as missing */ + TAILQ_FOREACH(entry, &device_tbl, link) + entry->flags &= ~HR_DEVICE_FOUND; + + if (devinfo_foreach_device_child(dev_root, device_collector, NULL)) + syslog(LOG_ERR, "%s: devinfo_foreach_device_child failed", + __func__); + + /* + * Purge items that disappeared + */ + TAILQ_FOREACH_SAFE(entry, &device_tbl, link, entry_tmp) { + /* + * If HR_DEVICE_IMMUTABLE bit is set then this means that + * this entry was not detected by the above + * devinfo_foreach_device() call. So we are not deleting + * it there. + */ + if (!(entry->flags & HR_DEVICE_FOUND) && + !(entry->flags & HR_DEVICE_IMMUTABLE)) + device_entry_delete(entry); + } + + device_tick = this_tick; + + /* + * Force a refresh for the hrDiskStorageTable + * XXX Why not the other dependen tables? + */ + refresh_disk_storage_tbl(1); + + out: + devinfo_free(); + act = 0; +} + +/** + * This is the implementation for a generated (by a SNMP tool) + * function prototype, see hostres_tree.h + * It handles the SNMP operations for hrDeviceTable + */ +int +op_hrDeviceTable(struct snmp_context *ctx __unused, struct snmp_value *value, + u_int sub, u_int iidx __unused, enum snmp_op curr_op) +{ + struct device_entry *entry; + + refresh_device_tbl(0); + + switch (curr_op) { + + case SNMP_OP_GETNEXT: + if ((entry = NEXT_OBJECT_INT(&device_tbl, + &value->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + value->var.len = sub + 1; + value->var.subs[sub] = entry->index; + goto get; + + case SNMP_OP_GET: + if ((entry = FIND_OBJECT_INT(&device_tbl, + &value->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + goto get; + + case SNMP_OP_SET: + if ((entry = FIND_OBJECT_INT(&device_tbl, + &value->var, sub)) == NULL) + return (SNMP_ERR_NO_CREATION); + return (SNMP_ERR_NOT_WRITEABLE); + + case SNMP_OP_ROLLBACK: + case SNMP_OP_COMMIT: + abort(); + } + abort(); + + get: + switch (value->var.subs[sub - 1]) { + + case LEAF_hrDeviceIndex: + value->v.integer = entry->index; + return (SNMP_ERR_NOERROR); + + case LEAF_hrDeviceType: + assert(entry->type != NULL); + value->v.oid = *(entry->type); + return (SNMP_ERR_NOERROR); + + case LEAF_hrDeviceDescr: + return (string_get(value, entry->descr, -1)); + + case LEAF_hrDeviceID: + value->v.oid = *(entry->id); + return (SNMP_ERR_NOERROR); + + case LEAF_hrDeviceStatus: + value->v.integer = entry->status; + return (SNMP_ERR_NOERROR); + + case LEAF_hrDeviceErrors: + value->v.uint32 = entry->errors; + return (SNMP_ERR_NOERROR); + } + abort(); +} |