diff options
Diffstat (limited to 'usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swrun_tbl.c')
-rw-r--r-- | usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swrun_tbl.c | 751 |
1 files changed, 751 insertions, 0 deletions
diff --git a/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swrun_tbl.c b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swrun_tbl.c new file mode 100644 index 0000000..bf9ecab --- /dev/null +++ b/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swrun_tbl.c @@ -0,0 +1,751 @@ +/* + * 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 for SNMPd. Implementation for hrSWRunTable + */ + +#include <sys/param.h> +#include <sys/proc.h> +#include <sys/sysctl.h> +#include <sys/user.h> +#include <sys/linker.h> + +#include <assert.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> + +#include "hostres_snmp.h" +#include "hostres_oid.h" +#include "hostres_tree.h" + + +/* + * Ugly thing: PID_MAX, NO_PID defined only in kernel + */ +#define NO_PID 100000 + +enum SWRunType { + SRT_UNKNOWN = 1, + SRT_OPERATING_SYSTEM = 2, + SRT_DEVICE_DRIVER = 3, + SRT_APPLICATION = 4 + +}; + +enum SWRunStatus { + SRS_RUNNING = 1, + SRS_RUNNABLE = 2, + SRS_NOT_RUNNABLE = 3, + SRS_INVALID = 4 +}; + +/* + * This structure is used to hold a SNMP table entry + * for both hrSWRunTable and hrSWRunPerfTable because + * hrSWRunPerfTable AUGMENTS hrSWRunTable + */ +struct swrun_entry { + int32_t index; + u_char name[64 + 1]; + struct asn_oid id; + u_char path[128 + 1]; + u_char parameters[128 + 1]; + int32_t type; /* enum SWRunType */ + int32_t status; /* enum SWRunStatus */ + int32_t perfCPU; + int32_t perfMemory; +#define HR_SWRUN_FOUND 0x001 + uint32_t flags; + uint64_t r_tick; /* tick when entry refreshed */ + TAILQ_ENTRY(swrun_entry) link; +}; +TAILQ_HEAD(swrun_tbl, swrun_entry); + +/* the head of the list with hrSWRunTable's entries */ +static struct swrun_tbl swrun_tbl = TAILQ_HEAD_INITIALIZER(swrun_tbl); + +/* last (agent) tick when hrSWRunTable and hrSWRunPerTable was updated */ +static uint64_t swrun_tick; + +/* maximum number of ticks between updates of SWRun and SWRunPerf table */ +uint32_t swrun_tbl_refresh = HR_SWRUN_TBL_REFRESH * 100; + +/* the value of the MIB object with the same name */ +static int32_t SWOSIndex; + +/** + * Malloc a new entry and add it to the list + * associated to this table. The item identified by + * the index parameter must not exist in this list. + */ +static struct swrun_entry * +swrun_entry_create(int32_t idx) +{ + struct swrun_entry *entry; + + if ((entry = malloc(sizeof(*entry))) == NULL) { + syslog(LOG_WARNING, "%s: %m", __func__); + return (NULL); + } + memset(entry, 0, sizeof(*entry)); + entry->index = idx; + + INSERT_OBJECT_INT(entry, &swrun_tbl); + return (entry); +} + +/** + * Unlink the entry from the list and then free its heap memory + */ +static void +swrun_entry_delete(struct swrun_entry *entry) +{ + + assert(entry != NULL); + + TAILQ_REMOVE(&swrun_tbl, entry, link); + + free(entry); +} + +/** + * Search one item by its index, return NULL if none found + */ +static struct swrun_entry * +swrun_entry_find_by_index(int32_t idx) +{ + struct swrun_entry *entry; + + TAILQ_FOREACH(entry, &swrun_tbl, link) + if (entry->index == idx) + return (entry); + return (NULL); +} + +/** + * Translate the kernel's process status to the SNMP one. + */ +static enum SWRunStatus +swrun_OS_get_proc_status(const struct kinfo_proc *kp) +{ + + assert(kp != NULL); + if(kp == NULL) { + return (SRS_INVALID); + } + + /* + * I'm using the old style flags - they look cleaner to me, + * at least for the purpose of this SNMP table + */ + switch (kp->ki_stat) { + + case SSTOP: + return (SRS_NOT_RUNNABLE); + + case SWAIT: + case SLOCK: + case SSLEEP: + return (SRS_RUNNABLE); + + case SZOMB: + return (SRS_INVALID); + + case SIDL: + case SRUN: + return (SRS_RUNNING); + + default: + syslog(LOG_ERR,"Unknown process state: %d", kp->ki_stat); + return (SRS_INVALID); + } +} + +/** + * Make an SNMP table entry from a kernel one. + */ +static void +kinfo_proc_to_swrun_entry(const struct kinfo_proc *kp, + struct swrun_entry *entry) +{ + char **argv = NULL; + uint64_t cpu_time = 0; + + strlcpy((char*)entry->name, kp->ki_ocomm, sizeof(entry->name)); + + entry->id = oid_zeroDotZero; /* unknown id - FIXME */ + + entry->path[0] = '\0'; + entry->parameters[0] = '\0'; + + assert(hrState_g.kd != NULL); + + argv = kvm_getargv(hr_kd, kp, sizeof(entry->parameters) - 1); + if(argv != NULL){ + memset(entry->parameters, '\0', sizeof(entry->parameters)); + + /* + * FIXME + * Path seems to not be available. + * Try to hack the info in argv[0]; + * this argv is under control of the program so this info + * is not realiable + */ + if(*argv != NULL && (*argv)[0] == '/') { + memset(entry->path, '\0', sizeof(entry->path)); + strlcpy((char*)entry->path, *argv, sizeof(entry->path)); + } + + argv++; /* skip the first one which was used for path */ + + while (argv != NULL && *argv != NULL ) { + if (entry->parameters[0] != 0) { + /* + * add a space between parameters, + * except before the first one + */ + strlcat((char *)entry->parameters, + " ", sizeof(entry->parameters)); + } + strlcat((char *)entry->parameters, *argv, + sizeof(entry->parameters)); + argv++; + } + } + + entry->type = (int32_t)(IS_KERNPROC(kp) ? SRT_OPERATING_SYSTEM : + SRT_APPLICATION); + + entry->status = (int32_t)swrun_OS_get_proc_status(kp); + cpu_time = kp->ki_runtime / 100000; /* centi-seconds */ + + /* may overflow the snmp type */ + entry->perfCPU = (cpu_time > (uint64_t)INT_MAX ? INT_MAX : cpu_time); + entry->perfMemory = kp->ki_size / 1024; /* in kilo-bytes */ + entry->r_tick = get_ticks(); +} + +/** + * Create a table entry for a KLD + */ +static void +kld_file_stat_to_swrun(const struct kld_file_stat *kfs, + struct swrun_entry *entry) +{ + + assert(kfs != NULL); + assert(entry != NULL); + + strlcpy((char *)entry->name, kfs->name, sizeof(entry->name)); + + /* FIXME: can we find the location where the module was loaded from? */ + entry->path[0] = '\0'; + + /* no parameters for kernel files (.ko) of for the kernel */ + entry->parameters[0] = '\0'; + + entry->id = oid_zeroDotZero; /* unknown id - FIXME */ + + if (strcmp(kfs->name, "kernel") == 0) { + entry->type = (int32_t)SRT_OPERATING_SYSTEM; + SWOSIndex = entry->index; + } else { + entry->type = (int32_t)SRT_DEVICE_DRIVER; /* well, not really */ + } + entry->status = (int32_t)SRS_RUNNING; + entry->perfCPU = 0; /* Info not available */ + entry->perfMemory = kfs->size / 1024; /* in kilo-bytes */ + entry->r_tick = get_ticks(); +} + +/** + * Get all visible proceses including the kernel visible threads + */ +static void +swrun_OS_get_procs(void) +{ + struct kinfo_proc *plist, *kp; + int i; + int nproc; + struct swrun_entry *entry; + + plist = kvm_getprocs(hr_kd, KERN_PROC_ALL, 0, &nproc); + if (plist == NULL || nproc < 0) { + syslog(LOG_ERR, "kvm_getprocs() failed: %m"); + return; + } + for (i = 0, kp = plist; i < nproc; i++, kp++) { + /* + * The SNMP table's index must begin from 1 (as specified by + * this table definition), the PIDs are starting from 0 + * so we are translating the PIDs to +1 + */ + entry = swrun_entry_find_by_index((int32_t)kp->ki_pid + 1); + if (entry == NULL) { + /* new entry - get memory for it */ + entry = swrun_entry_create((int32_t)kp->ki_pid + 1); + if (entry == NULL) + continue; + } + entry->flags |= HR_SWRUN_FOUND; /* mark it as found */ + + kinfo_proc_to_swrun_entry(kp, entry); + } +} + +/* + * Get kernel items: first the kernel itself, then the loaded modules. + */ +static void +swrun_OS_get_kinfo(void) +{ + int fileid; + struct swrun_entry *entry; + struct kld_file_stat stat; + + for (fileid = kldnext(0); fileid > 0; fileid = kldnext(fileid)) { + stat.version = sizeof(struct kld_file_stat); + if (kldstat(fileid, &stat) < 0) { + syslog(LOG_ERR, "kldstat() failed: %m"); + continue; + } + + /* + * kernel and kernel files (*.ko) will be indexed starting with + * NO_PID + 1; NO_PID is PID_MAX + 1 thus it will be no risk to + * overlap with real PIDs which are in range of 1 .. NO_PID + */ + entry = swrun_entry_find_by_index(NO_PID + 1 + stat.id); + if (entry == NULL) { + /* new entry - get memory for it */ + entry = swrun_entry_create(NO_PID + 1 + stat.id); + if (entry == NULL) + continue; + } + entry->flags |= HR_SWRUN_FOUND; /* mark it as found */ + + kld_file_stat_to_swrun(&stat, entry); + } +} + +/** + * Refresh the hrSWRun and hrSWRunPert tables. + */ +static void +refresh_swrun_tbl(void) +{ + + struct swrun_entry *entry, *entry_tmp; + + if (this_tick - swrun_tick < swrun_tbl_refresh) { + HRDBG("no refresh needed "); + return; + } + + /* mark each entry as missing */ + TAILQ_FOREACH(entry, &swrun_tbl, link) + entry->flags &= ~HR_SWRUN_FOUND; + + swrun_OS_get_procs(); + swrun_OS_get_kinfo(); + + /* + * Purge items that disappeared + */ + TAILQ_FOREACH_SAFE(entry, &swrun_tbl, link, entry_tmp) + if (!(entry->flags & HR_SWRUN_FOUND)) + swrun_entry_delete(entry); + + swrun_tick = this_tick; + + HRDBG("refresh DONE"); +} + +/** + * Update the information in this entry + */ +static void +fetch_swrun_entry(struct swrun_entry *entry) +{ + struct kinfo_proc *plist; + int nproc; + struct kld_file_stat stat; + + assert(entry != NULL); + + if (entry->index >= NO_PID + 1) { + /* + * kernel and kernel files (*.ko) will be indexed + * starting with NO_PID + 1; NO_PID is PID_MAX + 1 + * thus it will be no risk to overlap with real PIDs + * which are in range of 1 .. NO_PID + */ + stat.version = sizeof(stat); + if (kldstat(entry->index - NO_PID - 1, &stat) == -1) { + /* + * not found, it's gone. Mark it as invalid for now, it + * will be removed from the list at next global refersh + */ + HRDBG("missing item with kid=%d", + entry->index - NO_PID - 1); + entry->status = (int32_t)SRS_INVALID; + } else + kld_file_stat_to_swrun(&stat, entry); + + } else { + /* this is a process */ + assert(hrState_g.kd != NULL); + plist = kvm_getprocs(hr_kd, KERN_PROC_PID, + entry->index - 1, &nproc); + if (plist == NULL || nproc != 1) { + HRDBG("missing item with PID=%d", entry->index - 1); + entry->status = (int32_t)SRS_INVALID; + } else + kinfo_proc_to_swrun_entry(plist, entry); + } +} + +/** + * Invalidate entry. For KLDs we try to unload it, for processes we SIGKILL it. + */ +static int +invalidate_swrun_entry(struct swrun_entry *entry, int commit) +{ + struct kinfo_proc *plist; + int nproc; + struct kld_file_stat stat; + + assert(entry != NULL); + + if (entry->index >= NO_PID + 1) { + /* this is a kernel item */ + HRDBG("atempt to unload KLD %d", + entry->index - NO_PID - 1); + + if (entry->index == SWOSIndex) { + /* can't invalidate the kernel itself */ + return (SNMP_ERR_NOT_WRITEABLE); + } + + stat.version = sizeof(stat); + if (kldstat(entry->index - NO_PID - 1, &stat) == -1) { + /* + * not found, it's gone. Mark it as invalid for now, it + * will be removed from the list at next global + * refresh + */ + HRDBG("missing item with kid=%d", + entry->index - NO_PID - 1); + entry->status = (int32_t)SRS_INVALID; + return (SNMP_ERR_NOERROR); + } + /* + * There is no way to try to unload a module. There seems + * also no way to find out whether it is busy without unloading + * it. We can assume that it is busy, if the reference count + * is larger than 2, but if it is 1 nothing helps. + */ + if (!commit) { + if (stat.refs > 1) + return (SNMP_ERR_NOT_WRITEABLE); + return (SNMP_ERR_NOERROR); + } + if (kldunload(stat.id) == -1) { + syslog(LOG_ERR,"kldunload for %d/%s failed: %m", + stat.id, stat.name); + if (errno == EBUSY) + return (SNMP_ERR_NOT_WRITEABLE); + else + return (SNMP_ERR_RES_UNAVAIL); + } + } else { + /* this is a process */ + assert(hrState_g.kd != NULL); + + plist = kvm_getprocs(hr_kd, KERN_PROC_PID, + entry->index - 1, &nproc); + if (plist == NULL || nproc != 1) { + HRDBG("missing item with PID=%d", entry->index - 1); + entry->status = (int32_t)SRS_INVALID; + return (SNMP_ERR_NOERROR); + } + if (IS_KERNPROC(plist)) { + /* you don't want to do this */ + return (SNMP_ERR_NOT_WRITEABLE); + } + if (kill(entry->index - 1, commit ? SIGKILL : 0) < 0) { + syslog(LOG_ERR,"kill (%d, SIGKILL) failed: %m", + entry->index - 1); + if (errno == ESRCH) { + /* race: just gone */ + entry->status = (int32_t)SRS_INVALID; + return (SNMP_ERR_NOERROR); + } + return (SNMP_ERR_GENERR); + } + } + return (SNMP_ERR_NOERROR); +} + +/** + * Popuplate the hrSWRunTable. + */ +void +init_swrun_tbl(void) +{ + + refresh_swrun_tbl(); + HRDBG("done"); +} + +/** + * Finalize the hrSWRunTable. + */ +void +fini_swrun_tbl(void) +{ + struct swrun_entry *n1; + + while ((n1 = TAILQ_FIRST(&swrun_tbl)) != NULL) { + TAILQ_REMOVE(&swrun_tbl, n1, link); + free(n1); + } +} + +/* + * This is the implementation for a generated (by a SNMP tool) + * function prototype, see hostres_tree.h + * It hanldes the SNMP operations for hrSWRunTable + */ +int +op_hrSWRunTable(struct snmp_context *ctx __unused, struct snmp_value *value, + u_int sub, u_int iidx __unused, enum snmp_op curr_op) +{ + struct swrun_entry *entry; + int ret; + + refresh_swrun_tbl(); + + switch (curr_op) { + + case SNMP_OP_GETNEXT: + if ((entry = NEXT_OBJECT_INT(&swrun_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(&swrun_tbl, + &value->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + goto get; + + case SNMP_OP_SET: + if ((entry = FIND_OBJECT_INT(&swrun_tbl, + &value->var, sub)) == NULL) + return (SNMP_ERR_NO_CREATION); + + if (entry->r_tick < this_tick) + fetch_swrun_entry(entry); + + switch (value->var.subs[sub - 1]) { + + case LEAF_hrSWRunStatus: + if (value->v.integer != (int32_t)SRS_INVALID) + return (SNMP_ERR_WRONG_VALUE); + + if (entry->status == (int32_t)SRS_INVALID) + return (SNMP_ERR_NOERROR); + + /* + * Here we have a problem with the entire SNMP + * model: if we kill now, we cannot rollback. + * If we kill in the commit code, we cannot + * return an error. Because things may change between + * SET and COMMIT this is impossible to handle + * correctly. + */ + return (invalidate_swrun_entry(entry, 0)); + } + return (SNMP_ERR_NOT_WRITEABLE); + + case SNMP_OP_ROLLBACK: + return (SNMP_ERR_NOERROR); + + case SNMP_OP_COMMIT: + if ((entry = FIND_OBJECT_INT(&swrun_tbl, + &value->var, sub)) == NULL) + return (SNMP_ERR_NOERROR); + + switch (value->var.subs[sub - 1]) { + + case LEAF_hrSWRunStatus: + if (value->v.integer == (int32_t)SRS_INVALID && + entry->status != (int32_t)SRS_INVALID) + (void)invalidate_swrun_entry(entry, 1); + return (SNMP_ERR_NOERROR); + } + abort(); + } + abort(); + + get: + ret = SNMP_ERR_NOERROR; + switch (value->var.subs[sub - 1]) { + + case LEAF_hrSWRunIndex: + value->v.integer = entry->index; + break; + + case LEAF_hrSWRunName: + ret = string_get(value, entry->name, -1); + break; + + case LEAF_hrSWRunID: + value->v.oid = entry->id; + break; + + case LEAF_hrSWRunPath: + ret = string_get(value, entry->path, -1); + break; + + case LEAF_hrSWRunParameters: + ret = string_get(value, entry->parameters, -1); + break; + + case LEAF_hrSWRunType: + value->v.integer = entry->type; + break; + + case LEAF_hrSWRunStatus: + value->v.integer = entry->status; + break; + + default: + abort(); + } + return (ret); +} + +/** + * Scalar(s) in the SWRun group + */ +int +op_hrSWRun(struct snmp_context *ctx __unused, struct snmp_value *value, + u_int sub, u_int iidx __unused, enum snmp_op curr_op) +{ + + /* only SNMP GET is possible */ + switch (curr_op) { + + case SNMP_OP_GET: + goto get; + + case SNMP_OP_SET: + return (SNMP_ERR_NOT_WRITEABLE); + + case SNMP_OP_ROLLBACK: + case SNMP_OP_COMMIT: + case SNMP_OP_GETNEXT: + abort(); + } + abort(); + + get: + switch (value->var.subs[sub - 1]) { + + case LEAF_hrSWOSIndex: + value->v.uint32 = SWOSIndex; + return (SNMP_ERR_NOERROR); + + default: + abort(); + } +} + +/* + * This is the implementation for a generated (by a SNMP tool) + * function prototype, see hostres_tree.h + * It handles the SNMP operations for hrSWRunPerfTable + */ +int +op_hrSWRunPerfTable(struct snmp_context *ctx __unused, + struct snmp_value *value, u_int sub, u_int iidx __unused, + enum snmp_op curr_op ) +{ + struct swrun_entry *entry; + + refresh_swrun_tbl(); + + switch (curr_op) { + + case SNMP_OP_GETNEXT: + if ((entry = NEXT_OBJECT_INT(&swrun_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(&swrun_tbl, + &value->var, sub)) == NULL) + return (SNMP_ERR_NOSUCHNAME); + goto get; + + case SNMP_OP_SET: + if ((entry = FIND_OBJECT_INT(&swrun_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_hrSWRunPerfCPU: + value->v.integer = entry->perfCPU; + return (SNMP_ERR_NOERROR); + + case LEAF_hrSWRunPerfMem: + value->v.integer = entry->perfMemory; + return (SNMP_ERR_NOERROR); + } + abort(); +} |