Path: blob/main/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c
107563 views
/*-1* Copyright (c) 2005-2006 The FreeBSD Project2* All rights reserved.3*4* Author: Victor Cruceru <[email protected]>5*6* Redistribution of this software and documentation and use in source and7* binary forms, with or without modification, are permitted provided that8* the following conditions are met:9*10* 1. Redistributions of source code or documentation must retain the above11* copyright notice, this list of conditions and the following disclaimer.12* 2. Redistributions in binary form must reproduce the above copyright13* notice, this list of conditions and the following disclaimer in the14* documentation and/or other materials provided with the distribution.15*16* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND17* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE18* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE19* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE20* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL21* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS22* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)23* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT24* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY25* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF26* SUCH DAMAGE.27*/2829/*30* Host Resources MIB: hrDeviceTable implementation for SNMPd.31*/3233#include <sys/un.h>34#include <sys/limits.h>3536#include <assert.h>37#include <err.h>38#include <errno.h>39#include <stdlib.h>40#include <string.h>41#include <syslog.h>42#include <unistd.h>43#include <sysexits.h>4445#include "hostres_snmp.h"46#include "hostres_oid.h"47#include "hostres_tree.h"4849#define FREE_DEV_STRUCT(entry_p) do { \50free(entry_p->name); \51free(entry_p->location); \52free(entry_p->descr); \53free(entry_p); \54} while (0)5556/*57* Status of a device58*/59enum DeviceStatus {60DS_UNKNOWN = 1,61DS_RUNNING = 2,62DS_WARNING = 3,63DS_TESTING = 4,64DS_DOWN = 565};6667TAILQ_HEAD(device_tbl, device_entry);6869/* the head of the list with hrDeviceTable's entries */70static struct device_tbl device_tbl = TAILQ_HEAD_INITIALIZER(device_tbl);7172/* Table used for consistent device table indexing. */73struct device_map device_map = STAILQ_HEAD_INITIALIZER(device_map);7475/* next int available for indexing the hrDeviceTable */76static uint32_t next_device_index = 1;7778/* last (agent) tick when hrDeviceTable was updated */79static uint64_t device_tick = 0;8081/* maximum number of ticks between updates of device table */82uint32_t device_tbl_refresh = 10 * 100;8384/* socket for /var/run/devd.pipe */85static int devd_sock = -1;8687/* used to wait notifications from /var/run/devd.pipe */88static void *devd_fd;8990/* some constants */91static const struct asn_oid OIDX_hrDeviceProcessor_c = OIDX_hrDeviceProcessor;92static const struct asn_oid OIDX_hrDeviceOther_c = OIDX_hrDeviceOther;9394/**95* Create a new entry out of thin air.96*/97struct device_entry *98device_entry_create(const char *name, const char *location, const char *descr)99{100struct device_entry *entry = NULL;101struct device_map_entry *map = NULL;102size_t name_len;103size_t location_len;104105assert((name[0] != 0) || (location[0] != 0));106107if (name[0] == 0 && location[0] == 0)108return (NULL);109110STAILQ_FOREACH(map, &device_map, link) {111assert(map->name_key != NULL);112assert(map->location_key != NULL);113114if (strcmp(map->name_key, name) == 0 &&115strcmp(map->location_key, location) == 0) {116break;117}118}119120if (map == NULL) {121/* new object - get a new index */122if (next_device_index > INT_MAX) {123syslog(LOG_ERR,124"%s: hrDeviceTable index wrap", __func__);125/* There isn't much we can do here.126* If the next_swins_index is consumed127* then we can't add entries to this table128* So it is better to exit - if the table is sparsed129* at the next agent run we can fill it fully.130*/131errx(EX_SOFTWARE, "hrDeviceTable index wrap");132/* not reachable */133}134135if ((map = malloc(sizeof(*map))) == NULL) {136syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );137return (NULL);138}139140map->entry_p = NULL;141142name_len = strlen(name) + 1;143if (name_len > DEV_NAME_MLEN)144name_len = DEV_NAME_MLEN;145146if ((map->name_key = malloc(name_len)) == NULL) {147syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );148free(map);149return (NULL);150}151152location_len = strlen(location) + 1;153if (location_len > DEV_LOC_MLEN)154location_len = DEV_LOC_MLEN;155156if ((map->location_key = malloc(location_len )) == NULL) {157syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );158free(map->name_key);159free(map);160return (NULL);161}162163map->hrIndex = next_device_index++;164165strlcpy(map->name_key, name, name_len);166strlcpy(map->location_key, location, location_len);167168STAILQ_INSERT_TAIL(&device_map, map, link);169HRDBG("%s at %s added into hrDeviceMap at index=%d",170name, location, map->hrIndex);171} else {172HRDBG("%s at %s exists in hrDeviceMap index=%d",173name, location, map->hrIndex);174}175176if ((entry = malloc(sizeof(*entry))) == NULL) {177syslog(LOG_WARNING, "hrDeviceTable: %s: %m", __func__);178return (NULL);179}180memset(entry, 0, sizeof(*entry));181182entry->index = map->hrIndex;183map->entry_p = entry;184185if ((entry->name = strdup(map->name_key)) == NULL) {186syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );187free(entry);188return (NULL);189}190191if ((entry->location = strdup(map->location_key)) == NULL) {192syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );193free(entry->name);194free(entry);195return (NULL);196}197198/*199* From here till the end of this function we reuse name_len200* for a different purpose - for device_entry::descr201*/202if (name[0] != '\0')203name_len = strlen(name) + strlen(descr) +204strlen(": ") + 1;205else206name_len = strlen(location) + strlen(descr) +207strlen("unknown at : ") + 1;208209if (name_len > DEV_DESCR_MLEN)210name_len = DEV_DESCR_MLEN;211212if ((entry->descr = malloc(name_len )) == NULL) {213syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );214free(entry->name);215free(entry->location);216free(entry);217return (NULL);218}219220memset(&entry->descr[0], '\0', name_len);221222if (name[0] != '\0')223snprintf(entry->descr, name_len,224"%s: %s", name, descr);225else226snprintf(entry->descr, name_len,227"unknown at %s: %s", location, descr);228229entry->id = &oid_zeroDotZero; /* unknown id - FIXME */230entry->status = (u_int)DS_UNKNOWN;231entry->errors = 0;232entry->type = &OIDX_hrDeviceOther_c;233234INSERT_OBJECT_INT(entry, &device_tbl);235236return (entry);237}238239/**240* Create a new entry into the device table.241*/242static struct device_entry *243device_entry_create_devinfo(const struct devinfo_dev *dev_p)244{245246assert(dev_p->dd_name != NULL);247assert(dev_p->dd_location != NULL);248249return (device_entry_create(dev_p->dd_name, dev_p->dd_location,250dev_p->dd_desc));251}252253/**254* Delete an entry from the device table.255*/256void257device_entry_delete(struct device_entry *entry)258{259struct device_map_entry *map;260261assert(entry != NULL);262263TAILQ_REMOVE(&device_tbl, entry, link);264265STAILQ_FOREACH(map, &device_map, link)266if (map->entry_p == entry) {267map->entry_p = NULL;268break;269}270271FREE_DEV_STRUCT(entry);272}273274/**275* Find an entry given its name and location276*/277static struct device_entry *278device_find_by_dev(const struct devinfo_dev *dev_p)279{280struct device_map_entry *map;281282assert(dev_p != NULL);283284STAILQ_FOREACH(map, &device_map, link)285if (strcmp(map->name_key, dev_p->dd_name) == 0 &&286strcmp(map->location_key, dev_p->dd_location) == 0)287return (map->entry_p);288return (NULL);289}290291/**292* Find an entry given its index.293*/294struct device_entry *295device_find_by_index(int32_t idx)296{297struct device_entry *entry;298299TAILQ_FOREACH(entry, &device_tbl, link)300if (entry->index == idx)301return (entry);302return (NULL);303}304305/**306* Find an device entry given its name.307*/308struct device_entry *309device_find_by_name(const char *dev_name)310{311struct device_map_entry *map;312313assert(dev_name != NULL);314315STAILQ_FOREACH(map, &device_map, link)316if (strcmp(map->name_key, dev_name) == 0)317return (map->entry_p);318319return (NULL);320}321322/**323* Find out the type of device. CPU only currently.324*/325static void326device_get_type(struct devinfo_dev *dev_p, const struct asn_oid **out_type_p)327{328329assert(dev_p != NULL);330assert(out_type_p != NULL);331332if (dev_p == NULL)333return;334335if (strncmp(dev_p->dd_name, "cpu", strlen("cpu")) == 0 &&336strstr(dev_p->dd_location, ".CPU") != NULL) {337*out_type_p = &OIDX_hrDeviceProcessor_c;338return;339}340}341342/**343* Get the status of a device344*/345static enum DeviceStatus346device_get_status(struct devinfo_dev *dev)347{348349assert(dev != NULL);350351switch (dev->dd_state) {352case DS_ALIVE: /* probe succeeded */353case DS_NOTPRESENT: /* not probed or probe failed */354return (DS_DOWN);355case DS_ATTACHED: /* attach method called */356return (DS_RUNNING);357default:358return (DS_UNKNOWN);359}360}361362/**363* Get the info for the given device and then recursively process all364* child devices.365*/366static int367device_collector(struct devinfo_dev *dev, void *arg)368{369struct device_entry *entry;370371HRDBG("%llu/%llu name='%s' desc='%s' drivername='%s' location='%s'",372(unsigned long long)dev->dd_handle,373(unsigned long long)dev->dd_parent, dev->dd_name, dev->dd_desc,374dev->dd_drivername, dev->dd_location);375376if (dev->dd_name[0] != '\0' || dev->dd_location[0] != '\0') {377HRDBG("ANALYZING dev %s at %s",378dev->dd_name, dev->dd_location);379380if ((entry = device_find_by_dev(dev)) != NULL) {381entry->flags |= HR_DEVICE_FOUND;382entry->status = (u_int)device_get_status(dev);383} else if ((entry = device_entry_create_devinfo(dev)) != NULL) {384device_get_type(dev, &entry->type);385386entry->flags |= HR_DEVICE_FOUND;387entry->status = (u_int)device_get_status(dev);388}389} else {390HRDBG("SKIPPED unknown device at location '%s'",391dev->dd_location );392}393394return (devinfo_foreach_device_child(dev, device_collector, arg));395}396397/**398* Create the socket to the device daemon.399*/400static int401create_devd_socket(void)402{403int d_sock;404struct sockaddr_un devd_addr;405406bzero(&devd_addr, sizeof(struct sockaddr_un));407408if ((d_sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {409syslog(LOG_ERR, "Failed to create the socket for %s: %m",410PATH_DEVD_PIPE);411return (-1);412}413414devd_addr.sun_family = PF_LOCAL;415devd_addr.sun_len = sizeof(devd_addr);416strlcpy(devd_addr.sun_path, PATH_DEVD_PIPE,417sizeof(devd_addr.sun_path) - 1);418419if (connect(d_sock, (struct sockaddr *)&devd_addr,420sizeof(devd_addr)) == -1) {421syslog(LOG_ERR,"Failed to connect socket for %s: %m",422PATH_DEVD_PIPE);423if (close(d_sock) < 0 )424syslog(LOG_ERR,"Failed to close socket for %s: %m",425PATH_DEVD_PIPE);426return (-1);427}428429return (d_sock);430}431432/*433* Event on the devd socket.434*435* We should probably directly process entries here. For simplicity just436* call the refresh routine with the force flag for now.437*/438static void439devd_socket_callback(int fd, void *arg __unused)440{441char buf[512];442int read_len = -1;443444assert(fd == devd_sock);445446HRDBG("called");447448again:449read_len = read(fd, buf, sizeof(buf));450if (read_len < 0) {451if (errno == EBADF) {452devd_sock = -1;453if (devd_fd != NULL) {454fd_deselect(devd_fd);455devd_fd = NULL;456}457syslog(LOG_ERR, "Closing devd_fd, revert to "458"devinfo polling");459}460461} else if (read_len == 0) {462syslog(LOG_ERR, "zero bytes read from devd pipe... "463"closing socket!");464465if (close(devd_sock) < 0 )466syslog(LOG_ERR, "Failed to close devd socket: %m");467468devd_sock = -1;469if (devd_fd != NULL) {470fd_deselect(devd_fd);471devd_fd = NULL;472}473syslog(LOG_ERR, "Closing devd_fd, revert to devinfo polling");474475} else {476if (read_len == sizeof(buf))477goto again;478/* Only refresh device table on a device add or remove event. */479if (buf[0] == '+' || buf[0] == '-')480refresh_device_tbl(1);481}482}483484/**485* Initialize and populate the device table.486*/487void488init_device_tbl(void)489{490491/* initially populate table for the other tables */492refresh_device_tbl(1);493494/* no problem if that fails - just use polling mode */495devd_sock = create_devd_socket();496}497498/**499* Start devd(8) monitoring.500*/501void502start_device_tbl(struct lmodule *mod)503{504505if (devd_sock > 0) {506devd_fd = fd_select(devd_sock, devd_socket_callback, NULL, mod);507if (devd_fd == NULL)508syslog(LOG_ERR, "fd_select failed on devd socket: %m");509}510}511512/**513* Finalization routine for hrDeviceTable514* It destroys the lists and frees any allocated heap memory515*/516void517fini_device_tbl(void)518{519struct device_map_entry *n1;520521if (devd_fd != NULL)522fd_deselect(devd_fd);523524if (devd_sock != -1)525(void)close(devd_sock);526527devinfo_free();528529while ((n1 = STAILQ_FIRST(&device_map)) != NULL) {530STAILQ_REMOVE_HEAD(&device_map, link);531if (n1->entry_p != NULL) {532TAILQ_REMOVE(&device_tbl, n1->entry_p, link);533FREE_DEV_STRUCT(n1->entry_p);534}535free(n1->name_key);536free(n1->location_key);537free(n1);538}539assert(TAILQ_EMPTY(&device_tbl));540}541542/**543* Refresh routine for hrDeviceTable. We don't refresh here if the devd socket544* is open, because in this case we have the actual information always. We545* also don't refresh when the table is new enough (if we don't have a devd546* socket). In either case a refresh can be forced by passing a non-zero value.547*/548void549refresh_device_tbl(int force)550{551struct device_entry *entry, *entry_tmp;552struct devinfo_dev *dev_root;553static int act = 0;554555if (!force && (devd_sock >= 0 ||556(device_tick != 0 && this_tick - device_tick < device_tbl_refresh))){557HRDBG("no refresh needed");558return;559}560561if (act) {562syslog(LOG_ERR, "%s: recursive call", __func__);563return;564}565566if (devinfo_init() != 0) {567syslog(LOG_ERR,"%s: devinfo_init failed: %m", __func__);568return;569}570571act = 1;572if ((dev_root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL){573syslog(LOG_ERR, "%s: can't get the root device: %m", __func__);574goto out;575}576577/* mark each entry as missing */578TAILQ_FOREACH(entry, &device_tbl, link)579entry->flags &= ~HR_DEVICE_FOUND;580581if (devinfo_foreach_device_child(dev_root, device_collector, NULL))582syslog(LOG_ERR, "%s: devinfo_foreach_device_child failed",583__func__);584585/*586* Purge items that disappeared587*/588TAILQ_FOREACH_SAFE(entry, &device_tbl, link, entry_tmp) {589/*590* If HR_DEVICE_IMMUTABLE bit is set then this means that591* this entry was not detected by the above592* devinfo_foreach_device() call. So we are not deleting593* it there.594*/595if (!(entry->flags & HR_DEVICE_FOUND) &&596!(entry->flags & HR_DEVICE_IMMUTABLE))597device_entry_delete(entry);598}599600device_tick = this_tick;601602/*603* Force a refresh for the hrDiskStorageTable604* XXX Why not the other dependen tables?605*/606refresh_disk_storage_tbl(1);607608out:609devinfo_free();610act = 0;611}612613/**614* This is the implementation for a generated (by a SNMP tool)615* function prototype, see hostres_tree.h616* It handles the SNMP operations for hrDeviceTable617*/618int619op_hrDeviceTable(struct snmp_context *ctx __unused, struct snmp_value *value,620u_int sub, u_int iidx __unused, enum snmp_op curr_op)621{622struct device_entry *entry;623624refresh_device_tbl(0);625626switch (curr_op) {627628case SNMP_OP_GETNEXT:629if ((entry = NEXT_OBJECT_INT(&device_tbl,630&value->var, sub)) == NULL)631return (SNMP_ERR_NOSUCHNAME);632value->var.len = sub + 1;633value->var.subs[sub] = entry->index;634goto get;635636case SNMP_OP_GET:637if ((entry = FIND_OBJECT_INT(&device_tbl,638&value->var, sub)) == NULL)639return (SNMP_ERR_NOSUCHNAME);640goto get;641642case SNMP_OP_SET:643if ((entry = FIND_OBJECT_INT(&device_tbl,644&value->var, sub)) == NULL)645return (SNMP_ERR_NO_CREATION);646return (SNMP_ERR_NOT_WRITEABLE);647648case SNMP_OP_ROLLBACK:649case SNMP_OP_COMMIT:650abort();651}652abort();653654get:655switch (value->var.subs[sub - 1]) {656657case LEAF_hrDeviceIndex:658value->v.integer = entry->index;659return (SNMP_ERR_NOERROR);660661case LEAF_hrDeviceType:662assert(entry->type != NULL);663value->v.oid = *(entry->type);664return (SNMP_ERR_NOERROR);665666case LEAF_hrDeviceDescr:667return (string_get(value, entry->descr, -1));668669case LEAF_hrDeviceID:670value->v.oid = *(entry->id);671return (SNMP_ERR_NOERROR);672673case LEAF_hrDeviceStatus:674value->v.integer = entry->status;675return (SNMP_ERR_NOERROR);676677case LEAF_hrDeviceErrors:678value->v.uint32 = entry->errors;679return (SNMP_ERR_NOERROR);680}681abort();682}683684685