2 * Copyright (c) 2005-2006 The FreeBSD Project
5 * Author: Victor Cruceru <soc-victor@freebsd.org>
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:
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.
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
31 * Host Resources MIB for SNMPd. Implementation for hrSWRunTable
34 #include <sys/param.h>
36 #include <sys/sysctl.h>
38 #include <sys/linker.h>
46 #include "hostres_snmp.h"
47 #include "hostres_oid.h"
48 #include "hostres_tree.h"
51 * Ugly thing: PID_MAX, NO_PID defined only in kernel
57 SRT_OPERATING_SYSTEM = 2,
58 SRT_DEVICE_DRIVER = 3,
70 /* Maximum lengths for the strings according to the MIB */
71 #define SWR_NAME_MLEN (64 + 1)
72 #define SWR_PATH_MLEN (128 + 1)
73 #define SWR_PARAM_MLEN (128 + 1)
76 * This structure is used to hold a SNMP table entry
77 * for both hrSWRunTable and hrSWRunPerfTable because
78 * hrSWRunPerfTable AUGMENTS hrSWRunTable
82 u_char *name; /* it may be NULL */
83 const struct asn_oid *id;
84 u_char *path; /* it may be NULL */
85 u_char *parameters; /* it may be NULL */
86 int32_t type; /* enum SWRunType */
87 int32_t status; /* enum SWRunStatus */
90 #define HR_SWRUN_FOUND 0x001
92 uint64_t r_tick; /* tick when entry refreshed */
93 TAILQ_ENTRY(swrun_entry) link;
95 TAILQ_HEAD(swrun_tbl, swrun_entry);
97 /* the head of the list with hrSWRunTable's entries */
98 static struct swrun_tbl swrun_tbl = TAILQ_HEAD_INITIALIZER(swrun_tbl);
100 /* last (agent) tick when hrSWRunTable and hrSWRunPerTable was updated */
101 static uint64_t swrun_tick;
103 /* maximum number of ticks between updates of SWRun and SWRunPerf table */
104 uint32_t swrun_tbl_refresh = HR_SWRUN_TBL_REFRESH * 100;
106 /* the value of the MIB object with the same name */
107 static int32_t SWOSIndex;
110 * Malloc a new entry and add it to the list
111 * associated to this table. The item identified by
112 * the index parameter must not exist in this list.
114 static struct swrun_entry *
115 swrun_entry_create(int32_t idx)
117 struct swrun_entry *entry;
119 if ((entry = malloc(sizeof(*entry))) == NULL) {
120 syslog(LOG_WARNING, "%s: %m", __func__);
123 memset(entry, 0, sizeof(*entry));
126 INSERT_OBJECT_INT(entry, &swrun_tbl);
131 * Unlink the entry from the list and then free its heap memory
134 swrun_entry_delete(struct swrun_entry *entry)
137 assert(entry != NULL);
139 TAILQ_REMOVE(&swrun_tbl, entry, link);
143 free(entry->parameters);
148 * Search one item by its index, return NULL if none found
150 static struct swrun_entry *
151 swrun_entry_find_by_index(int32_t idx)
153 struct swrun_entry *entry;
155 TAILQ_FOREACH(entry, &swrun_tbl, link)
156 if (entry->index == idx)
162 * Translate the kernel's process status to SNMP.
164 static enum SWRunStatus
165 swrun_OS_get_proc_status(const struct kinfo_proc *kp)
170 return (SRS_INVALID);
174 * I'm using the old style flags - they look cleaner to me,
175 * at least for the purpose of this SNMP table
177 switch (kp->ki_stat) {
180 return (SRS_NOT_RUNNABLE);
185 return (SRS_RUNNABLE);
188 return (SRS_INVALID);
192 return (SRS_RUNNING);
195 syslog(LOG_ERR,"Unknown process state: %d", kp->ki_stat);
196 return (SRS_INVALID);
201 * Make an SNMP table entry from a kernel one.
204 kinfo_proc_to_swrun_entry(const struct kinfo_proc *kp,
205 struct swrun_entry *entry)
208 uint64_t cpu_time = 0;
211 pname_len = strlen(kp->ki_comm) + 1;
212 entry->name = reallocf(entry->name, pname_len);
213 if (entry->name != NULL)
214 strlcpy(entry->name, kp->ki_comm, pname_len);
216 entry->id = &oid_zeroDotZero; /* unknown id - FIXME */
218 assert(hr_kd != NULL);
220 argv = kvm_getargv(hr_kd, kp, SWR_PARAM_MLEN - 1);
222 u_char param[SWR_PARAM_MLEN];
224 memset(param, '\0', sizeof(param));
228 * Path seems to not be available.
229 * Try to hack the info in argv[0];
230 * this argv is under control of the program so this info
233 if(*argv != NULL && (*argv)[0] == '/') {
236 path_len = strlen(*argv) + 1;
237 if (path_len > SWR_PATH_MLEN)
238 path_len = SWR_PATH_MLEN;
240 entry->path = reallocf(entry->path, path_len);
241 if (entry->path != NULL) {
242 memset(entry->path, '\0', path_len);
243 strlcpy((char*)entry->path, *argv, path_len);
247 argv++; /* skip the first one which was used for path */
249 while (argv != NULL && *argv != NULL ) {
252 * add a space between parameters,
253 * except before the first one
255 strlcat((char *)param, " ", sizeof(param));
257 strlcat((char *)param, *argv, sizeof(param));
260 /* reuse pname_len */
261 pname_len = strlen(param) + 1;
262 if (pname_len > SWR_PARAM_MLEN)
263 pname_len = SWR_PARAM_MLEN;
265 entry->parameters = reallocf(entry->parameters, pname_len);
266 strlcpy(entry->parameters, param, pname_len);
269 entry->type = (int32_t)(IS_KERNPROC(kp) ? SRT_OPERATING_SYSTEM :
272 entry->status = (int32_t)swrun_OS_get_proc_status(kp);
273 cpu_time = kp->ki_runtime / 100000; /* centi-seconds */
275 /* may overflow the snmp type */
276 entry->perfCPU = (cpu_time > (uint64_t)INT_MAX ? INT_MAX : cpu_time);
277 entry->perfMemory = kp->ki_size / 1024; /* in kilo-bytes */
278 entry->r_tick = get_ticks();
282 * Create a table entry for a KLD
285 kld_file_stat_to_swrun(const struct kld_file_stat *kfs,
286 struct swrun_entry *entry)
291 assert(entry != NULL);
293 name_len = strlen(kfs->name) + 1;
294 if (name_len > SWR_NAME_MLEN)
295 name_len = SWR_NAME_MLEN;
297 entry->name = reallocf(entry->name, name_len);
298 if (entry->name != NULL)
299 strlcpy((char *)entry->name, kfs->name, name_len);
301 /* FIXME: can we find the location where the module was loaded from? */
304 /* no parameters for kernel files (.ko) of for the kernel */
305 entry->parameters = NULL;
307 entry->id = &oid_zeroDotZero; /* unknown id - FIXME */
309 if (strcmp(kfs->name, "kernel") == 0) {
310 entry->type = (int32_t)SRT_OPERATING_SYSTEM;
311 SWOSIndex = entry->index;
313 entry->type = (int32_t)SRT_DEVICE_DRIVER; /* well, not really */
315 entry->status = (int32_t)SRS_RUNNING;
316 entry->perfCPU = 0; /* Info not available */
317 entry->perfMemory = kfs->size / 1024; /* in kilo-bytes */
318 entry->r_tick = get_ticks();
322 * Get all visible proceses including the kernel visible threads
325 swrun_OS_get_procs(void)
327 struct kinfo_proc *plist, *kp;
330 struct swrun_entry *entry;
332 plist = kvm_getprocs(hr_kd, KERN_PROC_ALL, 0, &nproc);
333 if (plist == NULL || nproc < 0) {
334 syslog(LOG_ERR, "kvm_getprocs() failed: %m");
337 for (i = 0, kp = plist; i < nproc; i++, kp++) {
339 * The SNMP table's index must begin from 1 (as specified by
340 * this table definition), the PIDs are starting from 0
341 * so we are translating the PIDs to +1
343 entry = swrun_entry_find_by_index((int32_t)kp->ki_pid + 1);
345 /* new entry - get memory for it */
346 entry = swrun_entry_create((int32_t)kp->ki_pid + 1);
350 entry->flags |= HR_SWRUN_FOUND; /* mark it as found */
352 kinfo_proc_to_swrun_entry(kp, entry);
357 * Get kernel items: first the kernel itself, then the loaded modules.
360 swrun_OS_get_kinfo(void)
363 struct swrun_entry *entry;
364 struct kld_file_stat stat;
366 for (fileid = kldnext(0); fileid > 0; fileid = kldnext(fileid)) {
367 stat.version = sizeof(struct kld_file_stat);
368 if (kldstat(fileid, &stat) < 0) {
369 syslog(LOG_ERR, "kldstat() failed: %m");
374 * kernel and kernel files (*.ko) will be indexed starting with
375 * NO_PID + 1; NO_PID is PID_MAX + 1 thus it will be no risk to
376 * overlap with real PIDs which are in range of 1 .. NO_PID
378 entry = swrun_entry_find_by_index(NO_PID + 1 + stat.id);
380 /* new entry - get memory for it */
381 entry = swrun_entry_create(NO_PID + 1 + stat.id);
385 entry->flags |= HR_SWRUN_FOUND; /* mark it as found */
387 kld_file_stat_to_swrun(&stat, entry);
392 * Refresh the hrSWRun and hrSWRunPert tables.
395 refresh_swrun_tbl(void)
398 struct swrun_entry *entry, *entry_tmp;
400 if (this_tick - swrun_tick < swrun_tbl_refresh) {
401 HRDBG("no refresh needed ");
405 /* mark each entry as missing */
406 TAILQ_FOREACH(entry, &swrun_tbl, link)
407 entry->flags &= ~HR_SWRUN_FOUND;
409 swrun_OS_get_procs();
410 swrun_OS_get_kinfo();
413 * Purge items that disappeared
415 TAILQ_FOREACH_SAFE(entry, &swrun_tbl, link, entry_tmp)
416 if (!(entry->flags & HR_SWRUN_FOUND))
417 swrun_entry_delete(entry);
419 swrun_tick = this_tick;
421 HRDBG("refresh DONE");
425 * Update the information in this entry
428 fetch_swrun_entry(struct swrun_entry *entry)
430 struct kinfo_proc *plist;
432 struct kld_file_stat stat;
434 assert(entry != NULL);
436 if (entry->index >= NO_PID + 1) {
438 * kernel and kernel files (*.ko) will be indexed
439 * starting with NO_PID + 1; NO_PID is PID_MAX + 1
440 * thus it will be no risk to overlap with real PIDs
441 * which are in range of 1 .. NO_PID
443 stat.version = sizeof(stat);
444 if (kldstat(entry->index - NO_PID - 1, &stat) == -1) {
446 * not found, it's gone. Mark it as invalid for now, it
447 * will be removed from the list at next global refersh
449 HRDBG("missing item with kid=%d",
450 entry->index - NO_PID - 1);
451 entry->status = (int32_t)SRS_INVALID;
453 kld_file_stat_to_swrun(&stat, entry);
456 /* this is a process */
457 assert(hr_kd != NULL);
458 plist = kvm_getprocs(hr_kd, KERN_PROC_PID,
459 entry->index - 1, &nproc);
460 if (plist == NULL || nproc != 1) {
461 HRDBG("missing item with PID=%d", entry->index - 1);
462 entry->status = (int32_t)SRS_INVALID;
464 kinfo_proc_to_swrun_entry(plist, entry);
469 * Invalidate entry. For KLDs we try to unload it, for processes we SIGKILL it.
472 invalidate_swrun_entry(struct swrun_entry *entry, int commit)
474 struct kinfo_proc *plist;
476 struct kld_file_stat stat;
478 assert(entry != NULL);
480 if (entry->index >= NO_PID + 1) {
481 /* this is a kernel item */
482 HRDBG("atempt to unload KLD %d",
483 entry->index - NO_PID - 1);
485 if (entry->index == SWOSIndex) {
486 /* can't invalidate the kernel itself */
487 return (SNMP_ERR_NOT_WRITEABLE);
490 stat.version = sizeof(stat);
491 if (kldstat(entry->index - NO_PID - 1, &stat) == -1) {
493 * not found, it's gone. Mark it as invalid for now, it
494 * will be removed from the list at next global
497 HRDBG("missing item with kid=%d",
498 entry->index - NO_PID - 1);
499 entry->status = (int32_t)SRS_INVALID;
500 return (SNMP_ERR_NOERROR);
503 * There is no way to try to unload a module. There seems
504 * also no way to find out whether it is busy without unloading
505 * it. We can assume that it is busy, if the reference count
506 * is larger than 2, but if it is 1 nothing helps.
510 return (SNMP_ERR_NOT_WRITEABLE);
511 return (SNMP_ERR_NOERROR);
513 if (kldunload(stat.id) == -1) {
514 syslog(LOG_ERR,"kldunload for %d/%s failed: %m",
517 return (SNMP_ERR_NOT_WRITEABLE);
519 return (SNMP_ERR_RES_UNAVAIL);
522 /* this is a process */
523 assert(hr_kd != NULL);
525 plist = kvm_getprocs(hr_kd, KERN_PROC_PID,
526 entry->index - 1, &nproc);
527 if (plist == NULL || nproc != 1) {
528 HRDBG("missing item with PID=%d", entry->index - 1);
529 entry->status = (int32_t)SRS_INVALID;
530 return (SNMP_ERR_NOERROR);
532 if (IS_KERNPROC(plist)) {
533 /* you don't want to do this */
534 return (SNMP_ERR_NOT_WRITEABLE);
536 if (kill(entry->index - 1, commit ? SIGKILL : 0) < 0) {
537 syslog(LOG_ERR,"kill (%d, SIGKILL) failed: %m",
539 if (errno == ESRCH) {
540 /* race: just gone */
541 entry->status = (int32_t)SRS_INVALID;
542 return (SNMP_ERR_NOERROR);
544 return (SNMP_ERR_GENERR);
547 return (SNMP_ERR_NOERROR);
551 * Popuplate the hrSWRunTable.
562 * Finalize the hrSWRunTable.
567 struct swrun_entry *n1;
569 while ((n1 = TAILQ_FIRST(&swrun_tbl)) != NULL) {
570 TAILQ_REMOVE(&swrun_tbl, n1, link);
576 * This is the implementation for a generated (by a SNMP tool)
577 * function prototype, see hostres_tree.h
578 * It hanldes the SNMP operations for hrSWRunTable
581 op_hrSWRunTable(struct snmp_context *ctx __unused, struct snmp_value *value,
582 u_int sub, u_int iidx __unused, enum snmp_op curr_op)
584 struct swrun_entry *entry;
591 case SNMP_OP_GETNEXT:
592 if ((entry = NEXT_OBJECT_INT(&swrun_tbl,
593 &value->var, sub)) == NULL)
594 return (SNMP_ERR_NOSUCHNAME);
595 value->var.len = sub + 1;
596 value->var.subs[sub] = entry->index;
600 if ((entry = FIND_OBJECT_INT(&swrun_tbl,
601 &value->var, sub)) == NULL)
602 return (SNMP_ERR_NOSUCHNAME);
606 if ((entry = FIND_OBJECT_INT(&swrun_tbl,
607 &value->var, sub)) == NULL)
608 return (SNMP_ERR_NO_CREATION);
610 if (entry->r_tick < this_tick)
611 fetch_swrun_entry(entry);
613 switch (value->var.subs[sub - 1]) {
615 case LEAF_hrSWRunStatus:
616 if (value->v.integer != (int32_t)SRS_INVALID)
617 return (SNMP_ERR_WRONG_VALUE);
619 if (entry->status == (int32_t)SRS_INVALID)
620 return (SNMP_ERR_NOERROR);
623 * Here we have a problem with the entire SNMP
624 * model: if we kill now, we cannot rollback.
625 * If we kill in the commit code, we cannot
626 * return an error. Because things may change between
627 * SET and COMMIT this is impossible to handle
630 return (invalidate_swrun_entry(entry, 0));
632 return (SNMP_ERR_NOT_WRITEABLE);
634 case SNMP_OP_ROLLBACK:
635 return (SNMP_ERR_NOERROR);
638 if ((entry = FIND_OBJECT_INT(&swrun_tbl,
639 &value->var, sub)) == NULL)
640 return (SNMP_ERR_NOERROR);
642 switch (value->var.subs[sub - 1]) {
644 case LEAF_hrSWRunStatus:
645 if (value->v.integer == (int32_t)SRS_INVALID &&
646 entry->status != (int32_t)SRS_INVALID)
647 (void)invalidate_swrun_entry(entry, 1);
648 return (SNMP_ERR_NOERROR);
655 ret = SNMP_ERR_NOERROR;
656 switch (value->var.subs[sub - 1]) {
658 case LEAF_hrSWRunIndex:
659 value->v.integer = entry->index;
662 case LEAF_hrSWRunName:
663 if (entry->name != NULL)
664 ret = string_get(value, entry->name, -1);
666 ret = string_get(value, "", -1);
670 assert(entry->id != NULL);
671 value->v.oid = *entry->id;
674 case LEAF_hrSWRunPath:
675 if (entry->path != NULL)
676 ret = string_get(value, entry->path, -1);
678 ret = string_get(value, "", -1);
681 case LEAF_hrSWRunParameters:
682 if (entry->parameters != NULL)
683 ret = string_get(value, entry->parameters, -1);
685 ret = string_get(value, "", -1);
688 case LEAF_hrSWRunType:
689 value->v.integer = entry->type;
692 case LEAF_hrSWRunStatus:
693 value->v.integer = entry->status;
703 * Scalar(s) in the SWRun group
706 op_hrSWRun(struct snmp_context *ctx __unused, struct snmp_value *value,
707 u_int sub, u_int iidx __unused, enum snmp_op curr_op)
710 /* only SNMP GET is possible */
717 return (SNMP_ERR_NOT_WRITEABLE);
719 case SNMP_OP_ROLLBACK:
721 case SNMP_OP_GETNEXT:
727 switch (value->var.subs[sub - 1]) {
729 case LEAF_hrSWOSIndex:
730 value->v.uint32 = SWOSIndex;
731 return (SNMP_ERR_NOERROR);
739 * This is the implementation for a generated (by a SNMP tool)
740 * function prototype, see hostres_tree.h
741 * It handles the SNMP operations for hrSWRunPerfTable
744 op_hrSWRunPerfTable(struct snmp_context *ctx __unused,
745 struct snmp_value *value, u_int sub, u_int iidx __unused,
746 enum snmp_op curr_op )
748 struct swrun_entry *entry;
754 case SNMP_OP_GETNEXT:
755 if ((entry = NEXT_OBJECT_INT(&swrun_tbl,
756 &value->var, sub)) == NULL)
757 return (SNMP_ERR_NOSUCHNAME);
758 value->var.len = sub + 1;
759 value->var.subs[sub] = entry->index;
763 if ((entry = FIND_OBJECT_INT(&swrun_tbl,
764 &value->var, sub)) == NULL)
765 return (SNMP_ERR_NOSUCHNAME);
769 if ((entry = FIND_OBJECT_INT(&swrun_tbl,
770 &value->var, sub)) == NULL)
771 return (SNMP_ERR_NO_CREATION);
772 return (SNMP_ERR_NOT_WRITEABLE);
774 case SNMP_OP_ROLLBACK:
781 switch (value->var.subs[sub - 1]) {
783 case LEAF_hrSWRunPerfCPU:
784 value->v.integer = entry->perfCPU;
785 return (SNMP_ERR_NOERROR);
787 case LEAF_hrSWRunPerfMem:
788 value->v.integer = entry->perfMemory;
789 return (SNMP_ERR_NOERROR);