Path: blob/main/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swrun_tbl.c
109245 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*28* Host Resources MIB for SNMPd. Implementation for hrSWRunTable29*/3031#include <sys/param.h>32#include <sys/proc.h>33#include <sys/sysctl.h>34#include <sys/user.h>35#include <sys/linker.h>3637#include <assert.h>38#include <signal.h>39#include <stdlib.h>40#include <string.h>41#include <syslog.h>4243#include "hostres_snmp.h"44#include "hostres_oid.h"45#include "hostres_tree.h"4647/*48* Ugly thing: PID_MAX, NO_PID defined only in kernel49*/50#define NO_PID 1000005152enum SWRunType {53SRT_UNKNOWN = 1,54SRT_OPERATING_SYSTEM = 2,55SRT_DEVICE_DRIVER = 3,56SRT_APPLICATION = 45758};5960enum SWRunStatus {61SRS_RUNNING = 1,62SRS_RUNNABLE = 2,63SRS_NOT_RUNNABLE = 3,64SRS_INVALID = 465};6667/* Maximum lengths for the strings according to the MIB */68#define SWR_NAME_MLEN (64 + 1)69#define SWR_PATH_MLEN (128 + 1)70#define SWR_PARAM_MLEN (128 + 1)7172/*73* This structure is used to hold a SNMP table entry74* for both hrSWRunTable and hrSWRunPerfTable because75* hrSWRunPerfTable AUGMENTS hrSWRunTable76*/77struct swrun_entry {78int32_t index;79u_char *name; /* it may be NULL */80const struct asn_oid *id;81u_char *path; /* it may be NULL */82u_char *parameters; /* it may be NULL */83int32_t type; /* enum SWRunType */84int32_t status; /* enum SWRunStatus */85int32_t perfCPU;86int32_t perfMemory;87#define HR_SWRUN_FOUND 0x00188uint32_t flags;89uint64_t r_tick; /* tick when entry refreshed */90TAILQ_ENTRY(swrun_entry) link;91};92TAILQ_HEAD(swrun_tbl, swrun_entry);9394/* the head of the list with hrSWRunTable's entries */95static struct swrun_tbl swrun_tbl = TAILQ_HEAD_INITIALIZER(swrun_tbl);9697/* last (agent) tick when hrSWRunTable and hrSWRunPerTable was updated */98static uint64_t swrun_tick;99100/* maximum number of ticks between updates of SWRun and SWRunPerf table */101uint32_t swrun_tbl_refresh = HR_SWRUN_TBL_REFRESH * 100;102103/* the value of the MIB object with the same name */104static int32_t SWOSIndex;105106/**107* Malloc a new entry and add it to the list108* associated to this table. The item identified by109* the index parameter must not exist in this list.110*/111static struct swrun_entry *112swrun_entry_create(int32_t idx)113{114struct swrun_entry *entry;115116if ((entry = malloc(sizeof(*entry))) == NULL) {117syslog(LOG_WARNING, "%s: %m", __func__);118return (NULL);119}120memset(entry, 0, sizeof(*entry));121entry->index = idx;122123INSERT_OBJECT_INT(entry, &swrun_tbl);124return (entry);125}126127/**128* Unlink the entry from the list and then free its heap memory129*/130static void131swrun_entry_delete(struct swrun_entry *entry)132{133134assert(entry != NULL);135136TAILQ_REMOVE(&swrun_tbl, entry, link);137138free(entry->name);139free(entry->path);140free(entry->parameters);141free(entry);142}143144/**145* Search one item by its index, return NULL if none found146*/147static struct swrun_entry *148swrun_entry_find_by_index(int32_t idx)149{150struct swrun_entry *entry;151152TAILQ_FOREACH(entry, &swrun_tbl, link)153if (entry->index == idx)154return (entry);155return (NULL);156}157158/**159* Translate the kernel's process status to SNMP.160*/161static enum SWRunStatus162swrun_OS_get_proc_status(const struct kinfo_proc *kp)163{164165assert(kp != NULL);166if(kp == NULL) {167return (SRS_INVALID);168}169170/*171* I'm using the old style flags - they look cleaner to me,172* at least for the purpose of this SNMP table173*/174switch (kp->ki_stat) {175176case SSTOP:177return (SRS_NOT_RUNNABLE);178179case SWAIT:180case SLOCK:181case SSLEEP:182return (SRS_RUNNABLE);183184case SZOMB:185return (SRS_INVALID);186187case SIDL:188case SRUN:189return (SRS_RUNNING);190191default:192syslog(LOG_ERR,"Unknown process state: %d", kp->ki_stat);193return (SRS_INVALID);194}195}196197/**198* Make an SNMP table entry from a kernel one.199*/200static void201kinfo_proc_to_swrun_entry(const struct kinfo_proc *kp,202struct swrun_entry *entry)203{204char **argv = NULL;205uint64_t cpu_time = 0;206size_t pname_len;207208pname_len = strlen(kp->ki_comm) + 1;209entry->name = reallocf(entry->name, pname_len);210if (entry->name != NULL)211strlcpy(entry->name, kp->ki_comm, pname_len);212213entry->id = &oid_zeroDotZero; /* unknown id - FIXME */214215assert(hr_kd != NULL);216217argv = kvm_getargv(hr_kd, kp, SWR_PARAM_MLEN - 1);218if(argv != NULL){219u_char param[SWR_PARAM_MLEN];220221memset(param, '\0', sizeof(param));222223/*224* FIXME225* Path seems to not be available.226* Try to hack the info in argv[0];227* this argv is under control of the program so this info228* is not realiable229*/230if(*argv != NULL && (*argv)[0] == '/') {231size_t path_len;232233path_len = strlen(*argv) + 1;234if (path_len > SWR_PATH_MLEN)235path_len = SWR_PATH_MLEN;236237entry->path = reallocf(entry->path, path_len);238if (entry->path != NULL) {239memset(entry->path, '\0', path_len);240strlcpy((char*)entry->path, *argv, path_len);241}242}243244argv++; /* skip the first one which was used for path */245246while (argv != NULL && *argv != NULL ) {247if (param[0] != 0) {248/*249* add a space between parameters,250* except before the first one251*/252strlcat((char *)param, " ", sizeof(param));253}254strlcat((char *)param, *argv, sizeof(param));255argv++;256}257/* reuse pname_len */258pname_len = strlen(param) + 1;259if (pname_len > SWR_PARAM_MLEN)260pname_len = SWR_PARAM_MLEN;261262entry->parameters = reallocf(entry->parameters, pname_len);263strlcpy(entry->parameters, param, pname_len);264}265266entry->type = (int32_t)(IS_KERNPROC(kp) ? SRT_OPERATING_SYSTEM :267SRT_APPLICATION);268269entry->status = (int32_t)swrun_OS_get_proc_status(kp);270cpu_time = kp->ki_runtime / 100000; /* centi-seconds */271272/* may overflow the snmp type */273entry->perfCPU = (cpu_time > (uint64_t)INT_MAX ? INT_MAX : cpu_time);274entry->perfMemory = kp->ki_size / 1024; /* in kilo-bytes */275entry->r_tick = get_ticks();276}277278/**279* Create a table entry for a KLD280*/281static void282kld_file_stat_to_swrun(const struct kld_file_stat *kfs,283struct swrun_entry *entry)284{285size_t name_len;286287assert(kfs != NULL);288assert(entry != NULL);289290name_len = strlen(kfs->name) + 1;291if (name_len > SWR_NAME_MLEN)292name_len = SWR_NAME_MLEN;293294entry->name = reallocf(entry->name, name_len);295if (entry->name != NULL)296strlcpy((char *)entry->name, kfs->name, name_len);297298/* FIXME: can we find the location where the module was loaded from? */299entry->path = NULL;300301/* no parameters for kernel files (.ko) of for the kernel */302entry->parameters = NULL;303304entry->id = &oid_zeroDotZero; /* unknown id - FIXME */305306if (strcmp(kfs->name, "kernel") == 0) {307entry->type = (int32_t)SRT_OPERATING_SYSTEM;308SWOSIndex = entry->index;309} else {310entry->type = (int32_t)SRT_DEVICE_DRIVER; /* well, not really */311}312entry->status = (int32_t)SRS_RUNNING;313entry->perfCPU = 0; /* Info not available */314entry->perfMemory = kfs->size / 1024; /* in kilo-bytes */315entry->r_tick = get_ticks();316}317318/**319* Get all visible processes including the kernel visible threads320*/321static void322swrun_OS_get_procs(void)323{324struct kinfo_proc *plist, *kp;325int i;326int nproc;327struct swrun_entry *entry;328329plist = kvm_getprocs(hr_kd, KERN_PROC_ALL, 0, &nproc);330if (plist == NULL || nproc < 0) {331syslog(LOG_ERR, "kvm_getprocs() failed: %m");332return;333}334for (i = 0, kp = plist; i < nproc; i++, kp++) {335/*336* The SNMP table's index must begin from 1 (as specified by337* this table definition), the PIDs are starting from 0338* so we are translating the PIDs to +1339*/340entry = swrun_entry_find_by_index((int32_t)kp->ki_pid + 1);341if (entry == NULL) {342/* new entry - get memory for it */343entry = swrun_entry_create((int32_t)kp->ki_pid + 1);344if (entry == NULL)345continue;346}347entry->flags |= HR_SWRUN_FOUND; /* mark it as found */348349kinfo_proc_to_swrun_entry(kp, entry);350}351}352353/*354* Get kernel items: first the kernel itself, then the loaded modules.355*/356static void357swrun_OS_get_kinfo(void)358{359int fileid;360struct swrun_entry *entry;361struct kld_file_stat stat;362363for (fileid = kldnext(0); fileid > 0; fileid = kldnext(fileid)) {364stat.version = sizeof(struct kld_file_stat);365if (kldstat(fileid, &stat) < 0) {366syslog(LOG_ERR, "kldstat() failed: %m");367continue;368}369370/*371* kernel and kernel files (*.ko) will be indexed starting with372* NO_PID + 1; NO_PID is PID_MAX + 1 thus it will be no risk to373* overlap with real PIDs which are in range of 1 .. NO_PID374*/375entry = swrun_entry_find_by_index(NO_PID + 1 + stat.id);376if (entry == NULL) {377/* new entry - get memory for it */378entry = swrun_entry_create(NO_PID + 1 + stat.id);379if (entry == NULL)380continue;381}382entry->flags |= HR_SWRUN_FOUND; /* mark it as found */383384kld_file_stat_to_swrun(&stat, entry);385}386}387388/**389* Refresh the hrSWRun and hrSWRunPert tables.390*/391static void392refresh_swrun_tbl(void)393{394395struct swrun_entry *entry, *entry_tmp;396397if (this_tick - swrun_tick < swrun_tbl_refresh) {398HRDBG("no refresh needed ");399return;400}401402/* mark each entry as missing */403TAILQ_FOREACH(entry, &swrun_tbl, link)404entry->flags &= ~HR_SWRUN_FOUND;405406swrun_OS_get_procs();407swrun_OS_get_kinfo();408409/*410* Purge items that disappeared411*/412TAILQ_FOREACH_SAFE(entry, &swrun_tbl, link, entry_tmp)413if (!(entry->flags & HR_SWRUN_FOUND))414swrun_entry_delete(entry);415416swrun_tick = this_tick;417418HRDBG("refresh DONE");419}420421/**422* Update the information in this entry423*/424static void425fetch_swrun_entry(struct swrun_entry *entry)426{427struct kinfo_proc *plist;428int nproc;429struct kld_file_stat stat;430431assert(entry != NULL);432433if (entry->index >= NO_PID + 1) {434/*435* kernel and kernel files (*.ko) will be indexed436* starting with NO_PID + 1; NO_PID is PID_MAX + 1437* thus it will be no risk to overlap with real PIDs438* which are in range of 1 .. NO_PID439*/440stat.version = sizeof(stat);441if (kldstat(entry->index - NO_PID - 1, &stat) == -1) {442/*443* not found, it's gone. Mark it as invalid for now, it444* will be removed from the list at next global refersh445*/446HRDBG("missing item with kid=%d",447entry->index - NO_PID - 1);448entry->status = (int32_t)SRS_INVALID;449} else450kld_file_stat_to_swrun(&stat, entry);451452} else {453/* this is a process */454assert(hr_kd != NULL);455plist = kvm_getprocs(hr_kd, KERN_PROC_PID,456entry->index - 1, &nproc);457if (plist == NULL || nproc != 1) {458HRDBG("missing item with PID=%d", entry->index - 1);459entry->status = (int32_t)SRS_INVALID;460} else461kinfo_proc_to_swrun_entry(plist, entry);462}463}464465/**466* Invalidate entry. For KLDs we try to unload it, for processes we SIGKILL it.467*/468static int469invalidate_swrun_entry(struct swrun_entry *entry, int commit)470{471struct kinfo_proc *plist;472int nproc;473struct kld_file_stat stat;474475assert(entry != NULL);476477if (entry->index >= NO_PID + 1) {478/* this is a kernel item */479HRDBG("attempt to unload KLD %d",480entry->index - NO_PID - 1);481482if (entry->index == SWOSIndex) {483/* can't invalidate the kernel itself */484return (SNMP_ERR_NOT_WRITEABLE);485}486487stat.version = sizeof(stat);488if (kldstat(entry->index - NO_PID - 1, &stat) == -1) {489/*490* not found, it's gone. Mark it as invalid for now, it491* will be removed from the list at next global492* refresh493*/494HRDBG("missing item with kid=%d",495entry->index - NO_PID - 1);496entry->status = (int32_t)SRS_INVALID;497return (SNMP_ERR_NOERROR);498}499/*500* There is no way to try to unload a module. There seems501* also no way to find out whether it is busy without unloading502* it. We can assume that it is busy, if the reference count503* is larger than 2, but if it is 1 nothing helps.504*/505if (!commit) {506if (stat.refs > 1)507return (SNMP_ERR_NOT_WRITEABLE);508return (SNMP_ERR_NOERROR);509}510if (kldunload(stat.id) == -1) {511syslog(LOG_ERR,"kldunload for %d/%s failed: %m",512stat.id, stat.name);513if (errno == EBUSY)514return (SNMP_ERR_NOT_WRITEABLE);515else516return (SNMP_ERR_RES_UNAVAIL);517}518} else {519/* this is a process */520assert(hr_kd != NULL);521522plist = kvm_getprocs(hr_kd, KERN_PROC_PID,523entry->index - 1, &nproc);524if (plist == NULL || nproc != 1) {525HRDBG("missing item with PID=%d", entry->index - 1);526entry->status = (int32_t)SRS_INVALID;527return (SNMP_ERR_NOERROR);528}529if (IS_KERNPROC(plist)) {530/* you don't want to do this */531return (SNMP_ERR_NOT_WRITEABLE);532}533if (kill(entry->index - 1, commit ? SIGKILL : 0) < 0) {534syslog(LOG_ERR,"kill (%d, SIGKILL) failed: %m",535entry->index - 1);536if (errno == ESRCH) {537/* race: just gone */538entry->status = (int32_t)SRS_INVALID;539return (SNMP_ERR_NOERROR);540}541return (SNMP_ERR_GENERR);542}543}544return (SNMP_ERR_NOERROR);545}546547/**548* Populate the hrSWRunTable.549*/550void551init_swrun_tbl(void)552{553554refresh_swrun_tbl();555HRDBG("done");556}557558/**559* Finalize the hrSWRunTable.560*/561void562fini_swrun_tbl(void)563{564struct swrun_entry *n1;565566while ((n1 = TAILQ_FIRST(&swrun_tbl)) != NULL) {567TAILQ_REMOVE(&swrun_tbl, n1, link);568free(n1);569}570}571572/*573* This is the implementation for a generated (by a SNMP tool)574* function prototype, see hostres_tree.h575* It handles the SNMP operations for hrSWRunTable576*/577int578op_hrSWRunTable(struct snmp_context *ctx __unused, struct snmp_value *value,579u_int sub, u_int iidx __unused, enum snmp_op curr_op)580{581struct swrun_entry *entry;582int ret;583584refresh_swrun_tbl();585586switch (curr_op) {587588case SNMP_OP_GETNEXT:589if ((entry = NEXT_OBJECT_INT(&swrun_tbl,590&value->var, sub)) == NULL)591return (SNMP_ERR_NOSUCHNAME);592value->var.len = sub + 1;593value->var.subs[sub] = entry->index;594goto get;595596case SNMP_OP_GET:597if ((entry = FIND_OBJECT_INT(&swrun_tbl,598&value->var, sub)) == NULL)599return (SNMP_ERR_NOSUCHNAME);600goto get;601602case SNMP_OP_SET:603if ((entry = FIND_OBJECT_INT(&swrun_tbl,604&value->var, sub)) == NULL)605return (SNMP_ERR_NO_CREATION);606607if (entry->r_tick < this_tick)608fetch_swrun_entry(entry);609610switch (value->var.subs[sub - 1]) {611612case LEAF_hrSWRunStatus:613if (value->v.integer != (int32_t)SRS_INVALID)614return (SNMP_ERR_WRONG_VALUE);615616if (entry->status == (int32_t)SRS_INVALID)617return (SNMP_ERR_NOERROR);618619/*620* Here we have a problem with the entire SNMP621* model: if we kill now, we cannot rollback.622* If we kill in the commit code, we cannot623* return an error. Because things may change between624* SET and COMMIT this is impossible to handle625* correctly.626*/627return (invalidate_swrun_entry(entry, 0));628}629return (SNMP_ERR_NOT_WRITEABLE);630631case SNMP_OP_ROLLBACK:632return (SNMP_ERR_NOERROR);633634case SNMP_OP_COMMIT:635if ((entry = FIND_OBJECT_INT(&swrun_tbl,636&value->var, sub)) == NULL)637return (SNMP_ERR_NOERROR);638639switch (value->var.subs[sub - 1]) {640641case LEAF_hrSWRunStatus:642if (value->v.integer == (int32_t)SRS_INVALID &&643entry->status != (int32_t)SRS_INVALID)644(void)invalidate_swrun_entry(entry, 1);645return (SNMP_ERR_NOERROR);646}647abort();648}649abort();650651get:652ret = SNMP_ERR_NOERROR;653switch (value->var.subs[sub - 1]) {654655case LEAF_hrSWRunIndex:656value->v.integer = entry->index;657break;658659case LEAF_hrSWRunName:660if (entry->name != NULL)661ret = string_get(value, entry->name, -1);662else663ret = string_get(value, "", -1);664break;665666case LEAF_hrSWRunID:667assert(entry->id != NULL);668value->v.oid = *entry->id;669break;670671case LEAF_hrSWRunPath:672if (entry->path != NULL)673ret = string_get(value, entry->path, -1);674else675ret = string_get(value, "", -1);676break;677678case LEAF_hrSWRunParameters:679if (entry->parameters != NULL)680ret = string_get(value, entry->parameters, -1);681else682ret = string_get(value, "", -1);683break;684685case LEAF_hrSWRunType:686value->v.integer = entry->type;687break;688689case LEAF_hrSWRunStatus:690value->v.integer = entry->status;691break;692693default:694abort();695}696return (ret);697}698699/**700* Scalar(s) in the SWRun group701*/702int703op_hrSWRun(struct snmp_context *ctx __unused, struct snmp_value *value,704u_int sub, u_int iidx __unused, enum snmp_op curr_op)705{706707/* only SNMP GET is possible */708switch (curr_op) {709710case SNMP_OP_GET:711goto get;712713case SNMP_OP_SET:714return (SNMP_ERR_NOT_WRITEABLE);715716case SNMP_OP_ROLLBACK:717case SNMP_OP_COMMIT:718case SNMP_OP_GETNEXT:719abort();720}721abort();722723get:724switch (value->var.subs[sub - 1]) {725726case LEAF_hrSWOSIndex:727value->v.uint32 = SWOSIndex;728return (SNMP_ERR_NOERROR);729730default:731abort();732}733}734735/*736* This is the implementation for a generated (by a SNMP tool)737* function prototype, see hostres_tree.h738* It handles the SNMP operations for hrSWRunPerfTable739*/740int741op_hrSWRunPerfTable(struct snmp_context *ctx __unused,742struct snmp_value *value, u_int sub, u_int iidx __unused,743enum snmp_op curr_op )744{745struct swrun_entry *entry;746747refresh_swrun_tbl();748749switch (curr_op) {750751case SNMP_OP_GETNEXT:752if ((entry = NEXT_OBJECT_INT(&swrun_tbl,753&value->var, sub)) == NULL)754return (SNMP_ERR_NOSUCHNAME);755value->var.len = sub + 1;756value->var.subs[sub] = entry->index;757goto get;758759case SNMP_OP_GET:760if ((entry = FIND_OBJECT_INT(&swrun_tbl,761&value->var, sub)) == NULL)762return (SNMP_ERR_NOSUCHNAME);763goto get;764765case SNMP_OP_SET:766if ((entry = FIND_OBJECT_INT(&swrun_tbl,767&value->var, sub)) == NULL)768return (SNMP_ERR_NO_CREATION);769return (SNMP_ERR_NOT_WRITEABLE);770771case SNMP_OP_ROLLBACK:772case SNMP_OP_COMMIT:773abort();774}775abort();776777get:778switch (value->var.subs[sub - 1]) {779780case LEAF_hrSWRunPerfCPU:781value->v.integer = entry->perfCPU;782return (SNMP_ERR_NOERROR);783784case LEAF_hrSWRunPerfMem:785value->v.integer = entry->perfMemory;786return (SNMP_ERR_NOERROR);787}788abort();789}790791792