hostres_swrun_tbl.c revision 154249
1/*
2 * Copyright (c) 2005-2006 The FreeBSD Project
3 * All rights reserved.
4 *
5 * Author: Victor Cruceru <soc-victor@freebsd.org>
6 *
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:
10 *
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.
16 *
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
27 * SUCH DAMAGE.
28 *
29 * $FreeBSD: head/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_swrun_tbl.c 154249 2006-01-12 07:44:40Z harti $
30 *
31 * Host Resources MIB for SNMPd. Implementation for hrSWRunTable
32 */
33
34#include <sys/param.h>
35#include <sys/proc.h>
36#include <sys/sysctl.h>
37#include <sys/user.h>
38#include <sys/linker.h>
39
40#include <assert.h>
41#include <signal.h>
42#include <stdlib.h>
43#include <string.h>
44#include <syslog.h>
45
46#include "hostres_snmp.h"
47#include "hostres_oid.h"
48#include "hostres_tree.h"
49
50
51/*
52 * Ugly thing: PID_MAX, NO_PID defined only in kernel
53 */
54#define	NO_PID		100000
55
56enum SWRunType {
57	SRT_UNKNOWN		= 1,
58	SRT_OPERATING_SYSTEM	= 2,
59	SRT_DEVICE_DRIVER	= 3,
60	SRT_APPLICATION		= 4
61
62};
63
64enum SWRunStatus {
65	SRS_RUNNING		= 1,
66	SRS_RUNNABLE		= 2,
67	SRS_NOT_RUNNABLE	= 3,
68	SRS_INVALID		= 4
69};
70
71/*
72 * This structure is used to hold a SNMP table entry
73 * for both hrSWRunTable and hrSWRunPerfTable because
74 * hrSWRunPerfTable AUGMENTS hrSWRunTable
75 */
76struct swrun_entry {
77	int32_t		index;
78	u_char		name[64 + 1];
79	struct asn_oid	id;
80	u_char		path[128 + 1];
81	u_char		parameters[128 + 1];
82	int32_t		type;		/* enum SWRunType */
83	int32_t		status;		/* enum SWRunStatus */
84	int32_t		perfCPU;
85	int32_t		perfMemory;
86#define HR_SWRUN_FOUND 0x001
87	uint32_t	flags;
88	uint64_t	r_tick;		/* tick when entry refreshed */
89	TAILQ_ENTRY(swrun_entry) link;
90};
91TAILQ_HEAD(swrun_tbl, swrun_entry);
92
93/* the head of the list with hrSWRunTable's entries */
94static struct swrun_tbl swrun_tbl = TAILQ_HEAD_INITIALIZER(swrun_tbl);
95
96/* last (agent) tick when hrSWRunTable and hrSWRunPerTable was updated */
97static uint64_t swrun_tick;
98
99/* maximum number of ticks between updates of SWRun and SWRunPerf table */
100uint32_t swrun_tbl_refresh = HR_SWRUN_TBL_REFRESH * 100;
101
102/* the value of the MIB object with the same name */
103static int32_t SWOSIndex;
104
105/**
106 * Malloc a new entry and add it to the list
107 * associated to this table. The item identified by
108 * the index parameter must not exist in this list.
109 */
110static struct swrun_entry *
111swrun_entry_create(int32_t idx)
112{
113	struct swrun_entry *entry;
114
115	if ((entry = malloc(sizeof(*entry))) == NULL) {
116		syslog(LOG_WARNING, "%s: %m", __func__);
117		return (NULL);
118	}
119	memset(entry, 0, sizeof(*entry));
120	entry->index = idx;
121
122	INSERT_OBJECT_INT(entry, &swrun_tbl);
123	return (entry);
124}
125
126/**
127 * Unlink the entry from the list and then free its heap memory
128 */
129static void
130swrun_entry_delete(struct swrun_entry *entry)
131{
132
133	assert(entry != NULL);
134
135	TAILQ_REMOVE(&swrun_tbl, entry, link);
136
137	free(entry);
138}
139
140/**
141 * Search one item by its index, return NULL if none found
142 */
143static struct swrun_entry *
144swrun_entry_find_by_index(int32_t idx)
145{
146	struct swrun_entry *entry;
147
148	TAILQ_FOREACH(entry, &swrun_tbl, link)
149		if (entry->index == idx)
150			return (entry);
151	return (NULL);
152}
153
154/**
155 * Translate the kernel's process status to the SNMP one.
156 */
157static enum SWRunStatus
158swrun_OS_get_proc_status(const struct kinfo_proc *kp)
159{
160
161	assert(kp != NULL);
162	if(kp ==  NULL) {
163		return (SRS_INVALID);
164	}
165
166	/*
167	 * I'm using the old style flags - they look cleaner to me,
168	 * at least for the purpose of this SNMP table
169	 */
170	switch (kp->ki_stat) {
171
172	case SSTOP:
173		return (SRS_NOT_RUNNABLE);
174
175	case SWAIT:
176	case SLOCK:
177	case SSLEEP:
178		return (SRS_RUNNABLE);
179
180	case SZOMB:
181		return (SRS_INVALID);
182
183	case SIDL:
184	case SRUN:
185		return (SRS_RUNNING);
186
187	default:
188		syslog(LOG_ERR,"Unknown process state: %d", kp->ki_stat);
189		return (SRS_INVALID);
190	}
191}
192
193/**
194 * Make an SNMP table entry from a kernel one.
195 */
196static void
197kinfo_proc_to_swrun_entry(const struct kinfo_proc *kp,
198    struct swrun_entry *entry)
199{
200	char **argv = NULL;
201	uint64_t cpu_time = 0;
202
203	strlcpy((char*)entry->name, kp->ki_ocomm, sizeof(entry->name));
204
205	entry->id = oid_zeroDotZero; /* unknown id - FIXME */
206
207	entry->path[0] = '\0';
208	entry->parameters[0] = '\0';
209
210	assert(hr_kd != NULL);
211
212	argv = kvm_getargv(hr_kd, kp, sizeof(entry->parameters) - 1);
213	if(argv != NULL){
214		memset(entry->parameters, '\0', sizeof(entry->parameters));
215
216		/*
217		 * FIXME
218		 * Path seems to not be available.
219		 * Try to hack the info in argv[0];
220		 * this argv is under control of the program so this info
221		 * is not realiable
222		 */
223		if(*argv != NULL && (*argv)[0] == '/') {
224			memset(entry->path, '\0', sizeof(entry->path));
225			strlcpy((char*)entry->path, *argv, sizeof(entry->path));
226		}
227
228		argv++; /* skip the first one which was used for path */
229
230		while (argv != NULL && *argv != NULL ) {
231			if (entry->parameters[0] != 0)  {
232				/*
233				 * add a space between parameters,
234				 * except before the first one
235				 */
236				strlcat((char *)entry->parameters,
237				    " ", sizeof(entry->parameters));
238			}
239			strlcat((char *)entry->parameters, *argv,
240			    sizeof(entry->parameters));
241			argv++;
242		}
243	}
244
245	entry->type = (int32_t)(IS_KERNPROC(kp) ? SRT_OPERATING_SYSTEM :
246	    SRT_APPLICATION);
247
248	entry->status = (int32_t)swrun_OS_get_proc_status(kp);
249	cpu_time = kp->ki_runtime / 100000; /* centi-seconds */
250
251	/* may overflow the snmp type */
252	entry->perfCPU = (cpu_time > (uint64_t)INT_MAX ? INT_MAX : cpu_time);
253	entry->perfMemory = kp->ki_size / 1024; /* in kilo-bytes */
254	entry->r_tick = get_ticks();
255}
256
257/**
258 * Create a table entry for a KLD
259 */
260static void
261kld_file_stat_to_swrun(const struct kld_file_stat *kfs,
262    struct swrun_entry *entry)
263{
264
265	assert(kfs != NULL);
266	assert(entry != NULL);
267
268	strlcpy((char *)entry->name, kfs->name, sizeof(entry->name));
269
270	/* FIXME: can we find the location where the module was loaded from? */
271	entry->path[0] = '\0';
272
273	/* no parameters for kernel files (.ko) of for the kernel */
274	entry->parameters[0] = '\0';
275
276	entry->id = oid_zeroDotZero; /* unknown id - FIXME */
277
278	if (strcmp(kfs->name, "kernel") == 0) {
279		entry->type = (int32_t)SRT_OPERATING_SYSTEM;
280		SWOSIndex = entry->index;
281	} else {
282		entry->type = (int32_t)SRT_DEVICE_DRIVER; /* well, not really */
283	}
284	entry->status = (int32_t)SRS_RUNNING;
285	entry->perfCPU = 0;			/* Info not available */
286	entry->perfMemory = kfs->size / 1024;	/* in kilo-bytes */
287	entry->r_tick = get_ticks();
288}
289
290/**
291 * Get all visible proceses including the kernel visible threads
292 */
293static void
294swrun_OS_get_procs(void)
295{
296	struct kinfo_proc *plist, *kp;
297	int i;
298	int nproc;
299	struct swrun_entry *entry;
300
301	plist = kvm_getprocs(hr_kd, KERN_PROC_ALL, 0, &nproc);
302	if (plist == NULL || nproc < 0) {
303		syslog(LOG_ERR, "kvm_getprocs() failed: %m");
304		return;
305	}
306	for (i = 0, kp = plist; i < nproc; i++, kp++) {
307		/*
308		 * The SNMP table's index must begin from 1 (as specified by
309		 * this table definition), the PIDs are starting from 0
310		 * so we are translating the PIDs to +1
311		 */
312		entry = swrun_entry_find_by_index((int32_t)kp->ki_pid + 1);
313		if (entry == NULL) {
314			/* new entry - get memory for it */
315			entry = swrun_entry_create((int32_t)kp->ki_pid + 1);
316			if (entry == NULL)
317				continue;
318		}
319		entry->flags |= HR_SWRUN_FOUND;	/* mark it as found */
320
321		kinfo_proc_to_swrun_entry(kp, entry);
322	}
323}
324
325/*
326 * Get kernel items: first the kernel itself, then the loaded modules.
327 */
328static void
329swrun_OS_get_kinfo(void)
330{
331	int fileid;
332	struct swrun_entry *entry;
333	struct kld_file_stat stat;
334
335	for (fileid = kldnext(0); fileid > 0; fileid = kldnext(fileid)) {
336		stat.version = sizeof(struct kld_file_stat);
337		if (kldstat(fileid, &stat) < 0) {
338			syslog(LOG_ERR, "kldstat() failed: %m");
339			continue;
340		}
341
342		/*
343		 * kernel and kernel files (*.ko) will be indexed starting with
344		 * NO_PID + 1; NO_PID is PID_MAX + 1 thus it will be no risk to
345		 * overlap with real PIDs which are in range of 1 .. NO_PID
346		 */
347		entry = swrun_entry_find_by_index(NO_PID + 1 + stat.id);
348		if (entry == NULL) {
349			/* new entry - get memory for it */
350			entry = swrun_entry_create(NO_PID + 1 + stat.id);
351			if (entry == NULL)
352				continue;
353		}
354		entry->flags |= HR_SWRUN_FOUND; /* mark it as found */
355
356		kld_file_stat_to_swrun(&stat, entry);
357	}
358}
359
360/**
361 * Refresh the hrSWRun and hrSWRunPert tables.
362 */
363static void
364refresh_swrun_tbl(void)
365{
366
367	struct swrun_entry *entry, *entry_tmp;
368
369	if (this_tick - swrun_tick < swrun_tbl_refresh) {
370		HRDBG("no refresh needed ");
371		return;
372	}
373
374	/* mark each entry as missing */
375	TAILQ_FOREACH(entry, &swrun_tbl, link)
376		entry->flags &= ~HR_SWRUN_FOUND;
377
378	swrun_OS_get_procs();
379	swrun_OS_get_kinfo();
380
381	/*
382	 * Purge items that disappeared
383	 */
384	TAILQ_FOREACH_SAFE(entry, &swrun_tbl, link, entry_tmp)
385		if (!(entry->flags & HR_SWRUN_FOUND))
386			swrun_entry_delete(entry);
387
388	swrun_tick = this_tick;
389
390	HRDBG("refresh DONE");
391}
392
393/**
394 * Update the information in this entry
395 */
396static void
397fetch_swrun_entry(struct swrun_entry *entry)
398{
399	struct kinfo_proc *plist;
400	int nproc;
401	struct kld_file_stat stat;
402
403	assert(entry !=  NULL);
404
405	if (entry->index >= NO_PID + 1)	{
406		/*
407		 * kernel and kernel files (*.ko) will be indexed
408		 * starting with NO_PID + 1; NO_PID is PID_MAX + 1
409		 * thus it will be no risk to overlap with real PIDs
410		 * which are in range of 1 .. NO_PID
411		 */
412		stat.version = sizeof(stat);
413		if (kldstat(entry->index - NO_PID - 1, &stat) == -1) {
414			/*
415			 * not found, it's gone. Mark it as invalid for now, it
416			 * will be removed from the list at next global refersh
417			 */
418			 HRDBG("missing item with kid=%d",
419			     entry->index -  NO_PID - 1);
420			entry->status = (int32_t)SRS_INVALID;
421		} else
422			kld_file_stat_to_swrun(&stat, entry);
423
424	} else {
425		/* this is a process */
426		assert(hr_kd != NULL);
427		plist = kvm_getprocs(hr_kd, KERN_PROC_PID,
428		    entry->index - 1, &nproc);
429		if (plist == NULL || nproc != 1) {
430			HRDBG("missing item with PID=%d", entry->index - 1);
431			entry->status = (int32_t)SRS_INVALID;
432		} else
433			kinfo_proc_to_swrun_entry(plist, entry);
434	}
435}
436
437/**
438 * Invalidate entry. For KLDs we try to unload it, for processes we SIGKILL it.
439 */
440static int
441invalidate_swrun_entry(struct swrun_entry *entry, int commit)
442{
443	struct kinfo_proc *plist;
444	int nproc;
445	struct kld_file_stat stat;
446
447	assert(entry !=  NULL);
448
449	if (entry->index >= NO_PID + 1)	{
450		/* this is a kernel item */
451		HRDBG("atempt to unload KLD %d",
452		    entry->index -  NO_PID - 1);
453
454		if (entry->index == SWOSIndex) {
455			/* can't invalidate the kernel itself */
456			return (SNMP_ERR_NOT_WRITEABLE);
457		}
458
459		stat.version = sizeof(stat);
460		if (kldstat(entry->index - NO_PID - 1, &stat) == -1) {
461			/*
462			 * not found, it's gone. Mark it as invalid for now, it
463			 * will be removed from the list at next global
464			 * refresh
465			 */
466			HRDBG("missing item with kid=%d",
467			    entry->index - NO_PID - 1);
468			entry->status = (int32_t)SRS_INVALID;
469			return (SNMP_ERR_NOERROR);
470		}
471		/*
472		 * There is no way to try to unload a module. There seems
473		 * also no way to find out whether it is busy without unloading
474		 * it. We can assume that it is busy, if the reference count
475		 * is larger than 2, but if it is 1 nothing helps.
476		 */
477		if (!commit) {
478			if (stat.refs > 1)
479				return (SNMP_ERR_NOT_WRITEABLE);
480			return (SNMP_ERR_NOERROR);
481		}
482		if (kldunload(stat.id) == -1) {
483			syslog(LOG_ERR,"kldunload for %d/%s failed: %m",
484			    stat.id, stat.name);
485			if (errno == EBUSY)
486				return (SNMP_ERR_NOT_WRITEABLE);
487			else
488				return (SNMP_ERR_RES_UNAVAIL);
489		}
490	} else {
491		/* this is a process */
492		assert(hr_kd != NULL);
493
494		plist = kvm_getprocs(hr_kd, KERN_PROC_PID,
495		    entry->index - 1, &nproc);
496		if (plist == NULL || nproc != 1) {
497			HRDBG("missing item with PID=%d", entry->index - 1);
498			entry->status = (int32_t)SRS_INVALID;
499			return (SNMP_ERR_NOERROR);
500		}
501		if (IS_KERNPROC(plist)) {
502			/* you don't want to do this */
503			return (SNMP_ERR_NOT_WRITEABLE);
504		}
505		if (kill(entry->index - 1, commit ? SIGKILL : 0) < 0) {
506			syslog(LOG_ERR,"kill (%d, SIGKILL) failed: %m",
507			    entry->index - 1);
508			if (errno == ESRCH) {
509				/* race: just gone */
510				entry->status = (int32_t)SRS_INVALID;
511				return (SNMP_ERR_NOERROR);
512			}
513			return (SNMP_ERR_GENERR);
514		}
515	}
516	return (SNMP_ERR_NOERROR);
517}
518
519/**
520 * Popuplate the hrSWRunTable.
521 */
522void
523init_swrun_tbl(void)
524{
525
526	refresh_swrun_tbl();
527	HRDBG("done");
528}
529
530/**
531 * Finalize the hrSWRunTable.
532 */
533void
534fini_swrun_tbl(void)
535{
536	struct swrun_entry *n1;
537
538	while ((n1 = TAILQ_FIRST(&swrun_tbl)) != NULL) {
539		TAILQ_REMOVE(&swrun_tbl, n1, link);
540		free(n1);
541	}
542}
543
544/*
545 * This is the implementation for a generated (by a SNMP tool)
546 * function prototype, see hostres_tree.h
547 * It hanldes the SNMP operations for hrSWRunTable
548 */
549int
550op_hrSWRunTable(struct snmp_context *ctx __unused, struct snmp_value *value,
551    u_int sub, u_int iidx __unused, enum snmp_op curr_op)
552{
553	struct swrun_entry *entry;
554	int ret;
555
556	refresh_swrun_tbl();
557
558	switch (curr_op) {
559
560	  case SNMP_OP_GETNEXT:
561		if ((entry = NEXT_OBJECT_INT(&swrun_tbl,
562		    &value->var, sub)) == NULL)
563			return (SNMP_ERR_NOSUCHNAME);
564		value->var.len = sub + 1;
565		value->var.subs[sub] = entry->index;
566		goto get;
567
568	  case SNMP_OP_GET:
569		if ((entry = FIND_OBJECT_INT(&swrun_tbl,
570		    &value->var, sub)) == NULL)
571			return (SNMP_ERR_NOSUCHNAME);
572		goto get;
573
574	  case SNMP_OP_SET:
575		if ((entry = FIND_OBJECT_INT(&swrun_tbl,
576		    &value->var, sub)) == NULL)
577			return (SNMP_ERR_NO_CREATION);
578
579		if (entry->r_tick < this_tick)
580			fetch_swrun_entry(entry);
581
582		switch (value->var.subs[sub - 1]) {
583
584		case LEAF_hrSWRunStatus:
585			if (value->v.integer != (int32_t)SRS_INVALID)
586				return (SNMP_ERR_WRONG_VALUE);
587
588			if (entry->status == (int32_t)SRS_INVALID)
589				return (SNMP_ERR_NOERROR);
590
591			/*
592			 * Here we have a problem with the entire SNMP
593			 * model: if we kill now, we cannot rollback.
594			 * If we kill in the commit code, we cannot
595			 * return an error. Because things may change between
596			 * SET and COMMIT this is impossible to handle
597			 * correctly.
598			 */
599			return (invalidate_swrun_entry(entry, 0));
600		}
601		return (SNMP_ERR_NOT_WRITEABLE);
602
603	  case SNMP_OP_ROLLBACK:
604		return (SNMP_ERR_NOERROR);
605
606	  case SNMP_OP_COMMIT:
607		if ((entry = FIND_OBJECT_INT(&swrun_tbl,
608		    &value->var, sub)) == NULL)
609			return (SNMP_ERR_NOERROR);
610
611		switch (value->var.subs[sub - 1]) {
612
613		case LEAF_hrSWRunStatus:
614			if (value->v.integer == (int32_t)SRS_INVALID &&
615			    entry->status != (int32_t)SRS_INVALID)
616				(void)invalidate_swrun_entry(entry, 1);
617			return (SNMP_ERR_NOERROR);
618		}
619		abort();
620	}
621	abort();
622
623  get:
624	ret = SNMP_ERR_NOERROR;
625	switch (value->var.subs[sub - 1]) {
626
627	  case LEAF_hrSWRunIndex:
628		value->v.integer = entry->index;
629		break;
630
631	  case LEAF_hrSWRunName:
632	  	ret = string_get(value, entry->name, -1);
633	  	break;
634
635	  case LEAF_hrSWRunID:
636	  	value->v.oid = entry->id;
637	  	break;
638
639	  case 	LEAF_hrSWRunPath:
640	  	ret = string_get(value, entry->path, -1);
641	  	break;
642
643	  case LEAF_hrSWRunParameters:
644	  	ret = string_get(value, entry->parameters, -1);
645	  	break;
646
647	  case LEAF_hrSWRunType:
648	  	value->v.integer = entry->type;
649		break;
650
651	  case LEAF_hrSWRunStatus:
652	  	value->v.integer = entry->status;
653		break;
654
655	  default:
656	  	abort();
657	}
658	return (ret);
659}
660
661/**
662 * Scalar(s) in the SWRun group
663 */
664int
665op_hrSWRun(struct snmp_context *ctx __unused, struct snmp_value *value,
666    u_int sub, u_int iidx __unused, enum snmp_op curr_op)
667{
668
669	/* only SNMP GET is possible */
670	switch (curr_op) {
671
672	case SNMP_OP_GET:
673		goto get;
674
675	case SNMP_OP_SET:
676		return (SNMP_ERR_NOT_WRITEABLE);
677
678	case SNMP_OP_ROLLBACK:
679	case SNMP_OP_COMMIT:
680	case SNMP_OP_GETNEXT:
681		abort();
682	}
683	abort();
684
685  get:
686	switch (value->var.subs[sub - 1]) {
687
688	case LEAF_hrSWOSIndex:
689		value->v.uint32 = SWOSIndex;
690		return (SNMP_ERR_NOERROR);
691
692	default:
693		abort();
694	}
695}
696
697/*
698 * This is the implementation for a generated (by a SNMP tool)
699 * function prototype, see hostres_tree.h
700 * It handles the SNMP operations for hrSWRunPerfTable
701 */
702int
703op_hrSWRunPerfTable(struct snmp_context *ctx __unused,
704    struct snmp_value *value, u_int sub, u_int iidx __unused,
705    enum snmp_op curr_op )
706{
707	struct swrun_entry *entry;
708
709	refresh_swrun_tbl();
710
711	switch (curr_op) {
712
713	  case SNMP_OP_GETNEXT:
714		if ((entry = NEXT_OBJECT_INT(&swrun_tbl,
715		    &value->var, sub)) == NULL)
716			return (SNMP_ERR_NOSUCHNAME);
717		value->var.len = sub + 1;
718		value->var.subs[sub] = entry->index;
719		goto get;
720
721	  case SNMP_OP_GET:
722		if ((entry = FIND_OBJECT_INT(&swrun_tbl,
723		    &value->var, sub)) == NULL)
724			return (SNMP_ERR_NOSUCHNAME);
725		goto get;
726
727	  case SNMP_OP_SET:
728		if ((entry = FIND_OBJECT_INT(&swrun_tbl,
729		    &value->var, sub)) == NULL)
730			return (SNMP_ERR_NO_CREATION);
731		return (SNMP_ERR_NOT_WRITEABLE);
732
733	  case SNMP_OP_ROLLBACK:
734	  case SNMP_OP_COMMIT:
735	  	abort();
736	}
737	abort();
738
739  get:
740	switch (value->var.subs[sub - 1]) {
741
742	  case LEAF_hrSWRunPerfCPU:
743		value->v.integer = entry->perfCPU;
744		return (SNMP_ERR_NOERROR);
745
746	  case LEAF_hrSWRunPerfMem:
747		value->v.integer = entry->perfMemory;
748	  	return (SNMP_ERR_NOERROR);
749	}
750	abort();
751}
752