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 
30 /*
31  * Host Resources MIB for SNMPd. Implementation for hrStorageTable
32  */
33 
34 #include <sys/types.h>
35 #include <sys/param.h>
36 #include <sys/sysctl.h>
37 #include <sys/vmmeter.h>
38 #include <sys/mount.h>
39 
40 #include <vm/vm_param.h>
41 
42 #include <assert.h>
43 #include <err.h>
44 #include <limits.h>
45 #include <memstat.h>
46 #include <paths.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <syslog.h>
50 #include <unistd.h> /* for getpagesize() */
51 #include <sysexits.h>
52 
53 #include "hostres_snmp.h"
54 #include "hostres_oid.h"
55 #include "hostres_tree.h"
56 
57 /* maximum length for description string according to MIB */
58 #define	SE_DESC_MLEN	(255 + 1)
59 
60 /*
61  * This structure is used to hold a SNMP table entry
62  * for HOST-RESOURCES-MIB's hrStorageTable
63  */
64 struct storage_entry {
65 	int32_t		index;
66 	const struct asn_oid *type;
67 	u_char		*descr;
68 	int32_t		allocationUnits;
69 	int32_t		size;
70 	int32_t		used;
71 	uint32_t	allocationFailures;
72 #define	HR_STORAGE_FOUND 0x001
73 	uint32_t	flags;	/* to be used internally*/
74 	TAILQ_ENTRY(storage_entry) link;
75 };
76 TAILQ_HEAD(storage_tbl, storage_entry);
77 
78 /*
79  * Next structure is used to keep o list of mappings from a specific name
80  * (a_name) to an entry in the hrStorageTblEntry. We are trying to keep the
81  * same index for a specific name at least for the duration of one SNMP agent
82  * run.
83  */
84 struct storage_map_entry {
85 	int32_t		hrIndex; /* used for storage_entry::index */
86 
87 	/* map key, also used for storage_entry::descr */
88 	u_char		*a_name;
89 
90 	/*
91 	 * next may be NULL if the respective storage_entry
92 	 * is (temporally) gone
93 	 */
94 	struct storage_entry *entry;
95 	STAILQ_ENTRY(storage_map_entry) link;
96 };
97 STAILQ_HEAD(storage_map, storage_map_entry);
98 
99 /* the head of the list with table's entries */
100 static struct storage_tbl storage_tbl = TAILQ_HEAD_INITIALIZER(storage_tbl);
101 
102 /*for consistent table indexing*/
103 static struct storage_map storage_map =
104     STAILQ_HEAD_INITIALIZER(storage_map);
105 
106 /* last (agent) tick when hrStorageTable was updated */
107 static uint64_t storage_tick;
108 
109 /* maximum number of ticks between two refreshs */
110 uint32_t storage_tbl_refresh = HR_STORAGE_TBL_REFRESH * 100;
111 
112 /* for kvm_getswapinfo, malloc'd */
113 static struct kvm_swap *swap_devs;
114 static size_t swap_devs_len;		/* item count for swap_devs */
115 
116 /* for getfsstat, malloc'd */
117 static struct statfs *fs_buf;
118 static size_t fs_buf_count;		/* item count for fs_buf */
119 
120 static struct vmtotal mem_stats;
121 
122 /* next int available for indexing the hrStorageTable */
123 static uint32_t next_storage_index = 1;
124 
125 /* start of list for memory detailed stats */
126 static struct memory_type_list *mt_list;
127 
128 /* Constants */
129 static const struct asn_oid OIDX_hrStorageRam_c = OIDX_hrStorageRam;
130 static const struct asn_oid OIDX_hrStorageVirtualMemory_c =
131     OIDX_hrStorageVirtualMemory;
132 
133 /**
134  * Create a new entry into the storage table and, if necessary, an
135  * entry into the storage map.
136  */
137 static struct storage_entry *
138 storage_entry_create(const char *name)
139 {
140 	struct storage_entry *entry;
141 	struct storage_map_entry *map;
142 	size_t name_len;
143 
144 	assert(name != NULL);
145 	assert(strlen(name) > 0);
146 
147 	STAILQ_FOREACH(map, &storage_map, link)
148 		if (strcmp(map->a_name, name) == 0)
149 			break;
150 
151 	if (map == NULL) {
152 		/* new object - get a new index */
153 		if (next_storage_index > INT_MAX) {
154 			syslog(LOG_ERR,
155 			    "%s: hrStorageTable index wrap", __func__);
156 			errx(EX_SOFTWARE, "hrStorageTable index wrap");
157 		}
158 
159 		if ((map = malloc(sizeof(*map))) == NULL) {
160 			syslog(LOG_ERR, "hrStorageTable: %s: %m", __func__ );
161 			return (NULL);
162 		}
163 
164 		name_len = strlen(name) + 1;
165 		if (name_len > SE_DESC_MLEN)
166 			name_len = SE_DESC_MLEN;
167 
168 		if ((map->a_name = malloc(name_len)) == NULL) {
169 			free(map);
170 			return (NULL);
171 		}
172 
173 		strlcpy(map->a_name, name, name_len);
174 		map->hrIndex = next_storage_index++;
175 
176 		STAILQ_INSERT_TAIL(&storage_map, map, link);
177 
178 		HRDBG("%s added into hrStorageMap at index=%d",
179 		    name, map->hrIndex);
180 	} else {
181 		HRDBG("%s exists in hrStorageMap index=%d\n",
182 		    name, map->hrIndex);
183 	}
184 
185 	if ((entry = malloc(sizeof(*entry))) == NULL) {
186 		syslog(LOG_WARNING, "%s: %m", __func__);
187 		return (NULL);
188 	}
189 	memset(entry, 0, sizeof(*entry));
190 
191 	entry->index = map->hrIndex;
192 
193 	if ((entry->descr = strdup(map->a_name)) == NULL) {
194 		free(entry);
195 		return (NULL);
196 	}
197 
198 	map->entry = entry;
199 
200 	INSERT_OBJECT_INT(entry, &storage_tbl);
201 
202 	return (entry);
203 }
204 
205 /**
206  * Delete an entry from the storage table.
207  */
208 static void
209 storage_entry_delete(struct storage_entry *entry)
210 {
211 	struct storage_map_entry *map;
212 
213 	assert(entry != NULL);
214 
215 	TAILQ_REMOVE(&storage_tbl, entry, link);
216 	STAILQ_FOREACH(map, &storage_map, link)
217 		if (map->entry == entry) {
218 			map->entry = NULL;
219 			break;
220 		}
221 	free(entry->descr);
222 	free(entry);
223 }
224 
225 /**
226  * Find a table entry by its name.
227  */
228 static struct storage_entry *
229 storage_find_by_name(const char *name)
230 {
231 	struct storage_entry *entry;
232 
233 	TAILQ_FOREACH(entry, &storage_tbl, link)
234 		if (strcmp(entry->descr, name) == 0)
235 			return (entry);
236 
237 	return (NULL);
238 }
239 
240 /*
241  * VM info.
242  */
243 static void
244 storage_OS_get_vm(void)
245 {
246 	int mib[2] = { CTL_VM, VM_TOTAL };
247 	size_t len = sizeof(mem_stats);
248 	int page_size_bytes;
249 	struct storage_entry *entry;
250 
251 	if (sysctl(mib, 2, &mem_stats, &len, NULL, 0) < 0) {
252 		syslog(LOG_ERR,
253 		    "hrStoragetable: %s: sysctl({CTL_VM, VM_METER}) "
254 		    "failed: %m", __func__);
255 		assert(0);
256 		return;
257 	}
258 
259 	page_size_bytes = getpagesize();
260 
261 	/* Real Memory Metrics */
262 	if ((entry = storage_find_by_name("Real Memory Metrics")) == NULL &&
263 	    (entry = storage_entry_create("Real Memory Metrics")) == NULL)
264 		return; /* I'm out of luck now, maybe next time */
265 
266 	entry->flags |= HR_STORAGE_FOUND;
267 	entry->type = &OIDX_hrStorageRam_c;
268 	entry->allocationUnits = page_size_bytes;
269 	entry->size = mem_stats.t_rm;
270 	entry->used = mem_stats.t_arm; /* ACTIVE is not USED - FIXME */
271 	entry->allocationFailures = 0;
272 
273 	/* Shared Real Memory Metrics */
274 	if ((entry = storage_find_by_name("Shared Real Memory Metrics")) ==
275 	    NULL &&
276 	    (entry = storage_entry_create("Shared Real Memory Metrics")) ==
277 	    NULL)
278 		return;
279 
280 	entry->flags |= HR_STORAGE_FOUND;
281 	entry->type = &OIDX_hrStorageRam_c;
282 	entry->allocationUnits = page_size_bytes;
283 	entry->size = mem_stats.t_rmshr;
284 	/* ACTIVE is not USED - FIXME */
285 	entry->used = mem_stats.t_armshr;
286 	entry->allocationFailures = 0;
287 }
288 
289 static void
290 storage_OS_get_memstat(void)
291 {
292 	struct memory_type *mt_item;
293 	struct storage_entry *entry;
294 
295 	if (mt_list == NULL) {
296 		if ((mt_list = memstat_mtl_alloc()) == NULL)
297 			/* again? we have a serious problem */
298 		return;
299 	}
300 
301 	if (memstat_sysctl_all(mt_list, 0) < 0) {
302 		syslog(LOG_ERR, "memstat_sysctl_all failed: %s",
303 		    memstat_strerror(memstat_mtl_geterror(mt_list)) );
304 		return;
305 	}
306 
307 	if ((mt_item = memstat_mtl_first(mt_list)) == NULL) {
308 		/* usually this is not an error, no errno for this failure*/
309 		HRDBG("memstat_mtl_first failed");
310 		return;
311 	}
312 
313 	do {
314 		const char *memstat_name;
315 		uint64_t tmp_size;
316 		int allocator;
317 		char alloc_descr[SE_DESC_MLEN];
318 
319 		memstat_name = memstat_get_name(mt_item);
320 
321 		if (memstat_name == NULL || strlen(memstat_name) == 0)
322 			continue;
323 
324 		switch (allocator = memstat_get_allocator(mt_item)) {
325 
326 		  case ALLOCATOR_MALLOC:
327 			snprintf(alloc_descr, sizeof(alloc_descr),
328 			    "MALLOC: %s", memstat_name);
329 			break;
330 
331 		  case ALLOCATOR_UMA:
332 			snprintf(alloc_descr, sizeof(alloc_descr),
333 			    "UMA: %s", memstat_name);
334 			break;
335 
336 		  default:
337 			snprintf(alloc_descr, sizeof(alloc_descr),
338 			    "UNKNOWN%d: %s", allocator, memstat_name);
339 			break;
340 		}
341 
342 		if ((entry = storage_find_by_name(alloc_descr)) == NULL &&
343 		    (entry = storage_entry_create(alloc_descr)) == NULL)
344 			return;
345 
346 		entry->flags |= HR_STORAGE_FOUND;
347 		entry->type = &OIDX_hrStorageRam_c;
348 
349 		if ((tmp_size = memstat_get_size(mt_item)) == 0)
350 			tmp_size = memstat_get_sizemask(mt_item);
351 		entry->allocationUnits =
352 		    (tmp_size  > INT_MAX ? INT_MAX : (int32_t)tmp_size);
353 
354 		tmp_size  = memstat_get_countlimit(mt_item);
355 		entry->size =
356 		    (tmp_size  > INT_MAX ? INT_MAX : (int32_t)tmp_size);
357 
358 		tmp_size = memstat_get_count(mt_item);
359 		entry->used =
360 		    (tmp_size  > INT_MAX ? INT_MAX : (int32_t)tmp_size);
361 
362 		tmp_size = memstat_get_failures(mt_item);
363 		entry->allocationFailures =
364 		    (tmp_size  > INT_MAX ? INT_MAX : (int32_t)tmp_size);
365 
366 	} while((mt_item = memstat_mtl_next(mt_item)) != NULL);
367 }
368 
369 /**
370  * Get swap info
371  */
372 static void
373 storage_OS_get_swap(void)
374 {
375 	struct storage_entry *entry;
376 	char swap_w_prefix[SE_DESC_MLEN];
377 	size_t len;
378 	int nswapdev;
379 
380 	len = sizeof(nswapdev);
381 	nswapdev = 0;
382 
383 	if (sysctlbyname("vm.nswapdev", &nswapdev, &len, NULL,0 ) < 0) {
384 		syslog(LOG_ERR,
385 		    "hrStorageTable: sysctlbyname(\"vm.nswapdev\") "
386 		    "failed. %m");
387 		assert(0);
388 		return;
389 	}
390 
391 	if (nswapdev <= 0) {
392 		HRDBG("vm.nswapdev is %d", nswapdev);
393 		return;
394 	}
395 
396 	if (nswapdev + 1 != (int)swap_devs_len || swap_devs == NULL) {
397 		swap_devs_len = nswapdev + 1;
398 		swap_devs = reallocf(swap_devs,
399 		    swap_devs_len * sizeof(struct kvm_swap));
400 
401 		assert(swap_devs != NULL);
402 		if (swap_devs == NULL) {
403 			swap_devs_len = 0;
404 			return;
405 		}
406 	}
407 
408 	nswapdev = kvm_getswapinfo(hr_kd, swap_devs, swap_devs_len, 0);
409 	if (nswapdev < 0) {
410 		syslog(LOG_ERR,
411 		    "hrStorageTable: kvm_getswapinfo failed. %m\n");
412 		assert(0);
413 		return;
414 	}
415 
416 	for (len = 0; len < (size_t)nswapdev; len++) {
417 		memset(&swap_w_prefix[0], '\0', sizeof(swap_w_prefix));
418 		snprintf(swap_w_prefix, sizeof(swap_w_prefix) - 1,
419 		    "Swap:%s%s", _PATH_DEV, swap_devs[len].ksw_devname);
420 
421 		entry = storage_find_by_name(swap_w_prefix);
422 		if (entry == NULL)
423 			entry = storage_entry_create(swap_w_prefix);
424 
425 		assert (entry != NULL);
426 		if (entry == NULL)
427 			return; /* Out of luck */
428 
429 		entry->flags |= HR_STORAGE_FOUND;
430 		entry->type = &OIDX_hrStorageVirtualMemory_c;
431 		entry->allocationUnits = getpagesize();
432 		entry->size = swap_devs[len].ksw_total;
433 		entry->used = swap_devs[len].ksw_used;
434 		entry->allocationFailures = 0;
435 	}
436 }
437 
438 /**
439  * Query the underlaying OS for the mounted file systems
440  * anf fill in the respective lists (for hrStorageTable and for hrFSTable)
441  */
442 static void
443 storage_OS_get_fs(void)
444 {
445 	struct storage_entry *entry;
446 	uint64_t size, used;
447 	int i, mounted_fs_count, units;
448 	char fs_string[SE_DESC_MLEN];
449 
450 	if ((mounted_fs_count = getfsstat(NULL, 0, MNT_NOWAIT)) < 0) {
451 		syslog(LOG_ERR, "hrStorageTable: getfsstat() failed: %m");
452 		return; /* out of luck this time */
453 	}
454 
455 	if (mounted_fs_count != (int)fs_buf_count || fs_buf == NULL) {
456 		fs_buf_count = mounted_fs_count;
457 		fs_buf = reallocf(fs_buf, fs_buf_count * sizeof(struct statfs));
458 		if (fs_buf == NULL) {
459 			fs_buf_count = 0;
460 			assert(0);
461 			return;
462 		}
463 	}
464 
465 	if ((mounted_fs_count = getfsstat(fs_buf,
466 	    fs_buf_count * sizeof(struct statfs), MNT_NOWAIT)) < 0) {
467 		syslog(LOG_ERR, "hrStorageTable: getfsstat() failed: %m");
468 		return; /* out of luck this time */
469 	}
470 
471 	HRDBG("got %d mounted FS", mounted_fs_count);
472 
473 	fs_tbl_pre_refresh();
474 
475 	for (i = 0; i < mounted_fs_count; i++) {
476 		snprintf(fs_string, sizeof(fs_string),
477 		    "%s, type: %s, dev: %s", fs_buf[i].f_mntonname,
478 		    fs_buf[i].f_fstypename, fs_buf[i].f_mntfromname);
479 
480 		entry = storage_find_by_name(fs_string);
481 		if (entry == NULL)
482 			entry = storage_entry_create(fs_string);
483 
484 		assert (entry != NULL);
485 		if (entry == NULL)
486 			return; /* Out of luck */
487 
488 		entry->flags |= HR_STORAGE_FOUND;
489 		entry->type = fs_get_type(&fs_buf[i]); /*XXX - This is wrong*/
490 
491 		units = fs_buf[i].f_bsize;
492 		size = fs_buf[i].f_blocks;
493 		used = fs_buf[i].f_blocks - fs_buf[i].f_bfree;
494 		while (size > INT_MAX) {
495 			units <<= 1;
496 			size >>= 1;
497 			used >>= 1;
498 		}
499 		entry->allocationUnits = units;
500 		entry->size = size;
501 		entry->used = used;
502 
503 		entry->allocationFailures = 0;
504 
505 		/* take care of hrFSTable */
506 		fs_tbl_process_statfs_entry(&fs_buf[i], entry->index);
507 	}
508 
509 	fs_tbl_post_refresh();
510 }
511 
512 /**
513  * Initialize storage table and populate it.
514  */
515 void
516 init_storage_tbl(void)
517 {
518 	if ((mt_list = memstat_mtl_alloc()) == NULL)
519 		syslog(LOG_ERR,
520 		    "hrStorageTable: memstat_mtl_alloc() failed: %m");
521 
522 	refresh_storage_tbl(1);
523 }
524 
525 void
526 fini_storage_tbl(void)
527 {
528 	struct storage_map_entry *n1;
529 
530 	if (swap_devs != NULL) {
531 		free(swap_devs);
532 		swap_devs = NULL;
533 	}
534 	swap_devs_len = 0;
535 
536 	if (fs_buf != NULL) {
537 		free(fs_buf);
538 		fs_buf = NULL;
539 	}
540 	fs_buf_count = 0;
541 
542 	while ((n1 = STAILQ_FIRST(&storage_map)) != NULL) {
543 		STAILQ_REMOVE_HEAD(&storage_map, link);
544 		if (n1->entry != NULL) {
545 			TAILQ_REMOVE(&storage_tbl, n1->entry, link);
546 			free(n1->entry->descr);
547 			free(n1->entry);
548 		}
549 		free(n1->a_name);
550 		free(n1);
551 	}
552 	assert(TAILQ_EMPTY(&storage_tbl));
553 }
554 
555 void
556 refresh_storage_tbl(int force)
557 {
558 	struct storage_entry *entry, *entry_tmp;
559 
560 	if (!force && storage_tick != 0 &&
561 	    this_tick - storage_tick < storage_tbl_refresh) {
562 		HRDBG("no refresh needed");
563 		return;
564 	}
565 
566 	/* mark each entry as missing */
567 	TAILQ_FOREACH(entry, &storage_tbl, link)
568 		entry->flags &= ~HR_STORAGE_FOUND;
569 
570 	storage_OS_get_vm();
571 	storage_OS_get_swap();
572 	storage_OS_get_fs();
573 	storage_OS_get_memstat();
574 
575 	/*
576 	 * Purge items that disappeared
577 	 */
578 	TAILQ_FOREACH_SAFE(entry, &storage_tbl, link, entry_tmp)
579 		if (!(entry->flags & HR_STORAGE_FOUND))
580 			storage_entry_delete(entry);
581 
582 	storage_tick = this_tick;
583 
584 	HRDBG("refresh DONE");
585 }
586 
587 /*
588  * This is the implementation for a generated (by our SNMP tool)
589  * function prototype, see hostres_tree.h
590  * It handles the SNMP operations for hrStorageTable
591  */
592 int
593 op_hrStorageTable(struct snmp_context *ctx __unused, struct snmp_value *value,
594     u_int sub, u_int iidx __unused, enum snmp_op curr_op)
595 {
596 	struct storage_entry *entry;
597 
598 	refresh_storage_tbl(0);
599 
600 	switch (curr_op) {
601 
602 	case SNMP_OP_GETNEXT:
603 		if ((entry = NEXT_OBJECT_INT(&storage_tbl,
604 		    &value->var, sub)) == NULL)
605 			return (SNMP_ERR_NOSUCHNAME);
606 
607 		value->var.len = sub + 1;
608 		value->var.subs[sub] = entry->index;
609 		goto get;
610 
611 	case SNMP_OP_GET:
612 		if ((entry = FIND_OBJECT_INT(&storage_tbl,
613 		    &value->var, sub)) == NULL)
614 			return (SNMP_ERR_NOSUCHNAME);
615 		goto get;
616 
617 	case SNMP_OP_SET:
618 		if ((entry = FIND_OBJECT_INT(&storage_tbl,
619 		    &value->var, sub)) == NULL)
620 			return (SNMP_ERR_NO_CREATION);
621 		return (SNMP_ERR_NOT_WRITEABLE);
622 
623 	case SNMP_OP_ROLLBACK:
624 	case SNMP_OP_COMMIT:
625 		abort();
626 	}
627 	abort();
628 
629   get:
630 	switch (value->var.subs[sub - 1]) {
631 
632 	case LEAF_hrStorageIndex:
633 		value->v.integer = entry->index;
634 		return (SNMP_ERR_NOERROR);
635 
636 	case LEAF_hrStorageType:
637 		assert(entry->type != NULL);
638 		value->v.oid = *entry->type;
639 		return (SNMP_ERR_NOERROR);
640 
641 	case LEAF_hrStorageDescr:
642 		assert(entry->descr != NULL);
643 		return (string_get(value, entry->descr, -1));
644 		break;
645 
646 	case LEAF_hrStorageAllocationUnits:
647 		value->v.integer = entry->allocationUnits;
648 		return (SNMP_ERR_NOERROR);
649 
650 	case LEAF_hrStorageSize:
651 		value->v.integer = entry->size;
652 		return (SNMP_ERR_NOERROR);
653 
654 	case LEAF_hrStorageUsed:
655 		value->v.integer = entry->used;
656 		return (SNMP_ERR_NOERROR);
657 
658 	case LEAF_hrStorageAllocationFailures:
659 		value->v.uint32 = entry->allocationFailures;
660 		return (SNMP_ERR_NOERROR);
661 	}
662 	abort();
663 }
664