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$
30  */
31 
32 /*
33  * Host Resources MIB for SNMPd. Implementation for the hrDiskStorageTable
34  */
35 
36 #include <sys/types.h>
37 #include <sys/param.h>
38 #include <sys/ata.h>
39 #include <sys/disk.h>
40 #include <sys/linker.h>
41 #include <sys/mdioctl.h>
42 #include <sys/module.h>
43 #include <sys/sysctl.h>
44 
45 #include <assert.h>
46 #include <ctype.h>
47 #include <err.h>
48 #include <errno.h>
49 #include <paths.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <syslog.h>
53 #include <unistd.h>
54 
55 #include "hostres_snmp.h"
56 #include "hostres_oid.h"
57 #include "hostres_tree.h"
58 
59 enum hrDiskStrorageAccess {
60 	DS_READ_WRITE = 1,
61 	DS_READ_ONLY  = 2
62 };
63 
64 enum hrDiskStrorageMedia {
65 	DSM_OTHER	=	1,
66 	DSM_UNKNOWN	=	2,
67 	DSM_HARDDISK	=	3,
68 	DSM_FLOPPYDISK	=	4,
69 	DSM_OPTICALDISKROM=	5,
70 	DSM_OPTICALDISKWORM=	6,
71 	DSM_OPTICALDISKRW=	7,
72 	DSM_RAMDISK	=	8
73 };
74 
75 /*
76  * This structure is used to hold a SNMP table entry for HOST-RESOURCES-MIB's
77  * hrDiskStorageTable. Note that index is external being allocated and
78  * maintained by the hrDeviceTable code.
79  *
80  * NOTE: according to MIB removable means removable media, not the
81  * device itself (like a USB card reader)
82  */
83 struct disk_entry {
84 	int32_t		index;
85 	int32_t		access;		/* enum hrDiskStrorageAccess */
86 	int32_t		media;		/* enum hrDiskStrorageMedia*/
87 	int32_t		removable; 	/* enum snmpTCTruthValue*/
88 	int32_t		capacity;
89 	TAILQ_ENTRY(disk_entry) link;
90 	/*
91 	 * next items are not from the SNMP mib table, only to be used
92 	 * internally
93 	 */
94 #define HR_DISKSTORAGE_FOUND	0x001
95 #define HR_DISKSTORAGE_ATA	0x002 /* belongs to the ATA subsystem */
96 #define HR_DISKSTORAGE_MD	0x004 /* it is a MD (memory disk) */
97 	uint32_t	flags;
98 	uint64_t	r_tick;
99 	u_char		dev_name[32];	/* device name, i.e. "ad4" or "acd0" */
100 };
101 TAILQ_HEAD(disk_tbl, disk_entry);
102 
103 /* the head of the list with hrDiskStorageTable's entries */
104 static struct disk_tbl disk_tbl =
105     TAILQ_HEAD_INITIALIZER(disk_tbl);
106 
107 /* last tick when hrFSTable was updated */
108 static uint64_t disk_storage_tick;
109 
110 /* minimum number of ticks between refreshs */
111 uint32_t disk_storage_tbl_refresh = HR_DISK_TBL_REFRESH * 100;
112 
113 /* fd for "/dev/mdctl"*/
114 static int md_fd = -1;
115 
116 /* buffer for sysctl("kern.disks") */
117 static char *disk_list;
118 static size_t disk_list_len;
119 
120 /* some constants */
121 static const struct asn_oid OIDX_hrDeviceDiskStorage_c =
122     OIDX_hrDeviceDiskStorage;
123 
124 /**
125  * Load the MD driver if it isn't loaded already.
126  */
127 static void
128 mdmaybeload(void)
129 {
130 	char name1[64], name2[64];
131 
132 	snprintf(name1, sizeof(name1), "g_%s", MD_NAME);
133 	snprintf(name2, sizeof(name2), "geom_%s", MD_NAME);
134 	if (modfind(name1) == -1) {
135 		/* Not present in kernel, try loading it. */
136 		if (kldload(name2) == -1 || modfind(name1) == -1) {
137 			if (errno != EEXIST) {
138 				errx(EXIT_FAILURE,
139 				    "%s module not available!", name2);
140 			}
141 		}
142 	}
143 }
144 
145 /**
146  * Create a new entry into the DiskStorageTable.
147  */
148 static struct disk_entry *
149 disk_entry_create(const struct device_entry *devEntry)
150 {
151 	struct disk_entry *entry;
152 
153 	assert(devEntry != NULL);
154 	if (devEntry == NULL)
155 		return NULL;
156 
157 	if ((entry = malloc(sizeof(*entry))) == NULL) {
158 		syslog(LOG_WARNING, "hrDiskStorageTable: %s: %m", __func__);
159 		return (NULL);
160 	}
161 
162 	memset(entry, 0, sizeof(*entry));
163 	entry->index = devEntry->index;
164 	INSERT_OBJECT_INT(entry, &disk_tbl);
165 
166 	return (entry);
167 }
168 
169 /**
170  * Delete a disk table entry.
171  */
172 static void
173 disk_entry_delete(struct disk_entry *entry)
174 {
175 	struct device_entry *devEntry;
176 
177 	assert(entry != NULL);
178 	TAILQ_REMOVE(&disk_tbl, entry, link);
179 
180 	devEntry = device_find_by_index(entry->index);
181 
182 	free(entry);
183 
184 	/*
185 	 * Also delete the respective device entry -
186 	 * this is needed for disk devices that are not
187 	 * detected by libdevinfo
188 	 */
189 	if (devEntry != NULL &&
190 	    (devEntry->flags & HR_DEVICE_IMMUTABLE) == HR_DEVICE_IMMUTABLE)
191 		device_entry_delete(devEntry);
192 }
193 
194 /**
195  * Find a disk storage entry given its index.
196  */
197 static struct disk_entry *
198 disk_find_by_index(int32_t idx)
199 {
200 	struct disk_entry *entry;
201 
202 	TAILQ_FOREACH(entry, &disk_tbl, link)
203 		if (entry->index == idx)
204 			return (entry);
205 
206 	return (NULL);
207 }
208 
209 /**
210  * Get the disk parameters
211  */
212 static void
213 disk_query_disk(struct disk_entry *entry)
214 {
215 	char dev_path[128];
216 	int fd;
217 	off_t mediasize;
218 
219 	if (entry == NULL || entry->dev_name[0] == '\0')
220 		return;
221 
222 	snprintf(dev_path, sizeof(dev_path),
223 	    "%s%s", _PATH_DEV, entry->dev_name);
224 	entry->capacity = 0;
225 
226 	HRDBG("OPENING device %s", dev_path);
227 	if ((fd = open(dev_path, O_RDONLY|O_NONBLOCK)) == -1) {
228 		HRDBG("OPEN device %s failed: %s", dev_path, strerror(errno));
229 		return;
230 	}
231 
232 	if (ioctl(fd, DIOCGMEDIASIZE, &mediasize) < 0) {
233 		HRDBG("DIOCGMEDIASIZE for device %s failed: %s",
234 		    dev_path, strerror(errno));
235 		(void)close(fd);
236 		return;
237 	}
238 
239 	mediasize = mediasize / 1024;
240 	entry->capacity = (mediasize > INT_MAX ? INT_MAX : mediasize);
241 	partition_tbl_handle_disk(entry->index, entry->dev_name);
242 
243 	(void)close(fd);
244 }
245 
246 /**
247  * Find all ATA disks in the device table.
248  */
249 static void
250 disk_OS_get_ATA_disks(void)
251 {
252 	struct device_map_entry *map;
253 	struct device_entry *entry;
254 	struct disk_entry *disk_entry;
255 	const struct disk_entry *found;
256 
257 	/* Things we know are ata disks */
258 	static const struct disk_entry lookup[] = {
259 		{
260 		    .dev_name = "ad",
261 		    .media = DSM_HARDDISK,
262 		    .removable = SNMP_FALSE
263 		},
264 		{
265 		    .dev_name = "ar",
266 		    .media = DSM_OTHER,
267 		    .removable = SNMP_FALSE
268 		},
269 		{
270 		    .dev_name = "acd",
271 		    .media = DSM_OPTICALDISKROM,
272 		    .removable = SNMP_TRUE
273 		},
274 		{
275 		    .dev_name = "afd",
276 		    .media = DSM_FLOPPYDISK,
277 		    .removable = SNMP_TRUE
278 		},
279 		{
280 		    .dev_name = "ast",
281 		    .media = DSM_OTHER,
282 		    .removable = SNMP_TRUE
283 		},
284 
285 		{ .media = DSM_UNKNOWN }
286 	};
287 
288 	/* Walk over the device table looking for ata disks */
289 	STAILQ_FOREACH(map, &device_map, link) {
290 		/* Skip deleted entries. */
291 		if (map->entry_p == NULL)
292 			continue;
293 		for (found = lookup; found->media != DSM_UNKNOWN; found++) {
294 			if (strncmp(map->name_key, found->dev_name,
295 			    strlen(found->dev_name)) != 0)
296 				continue;
297 
298 			/*
299 			 * Avoid false disk devices. For example adw(4) and
300 			 * adv(4) - they are not disks!
301 			 */
302 			if (strlen(map->name_key) > strlen(found->dev_name) &&
303 			    !isdigit(map->name_key[strlen(found->dev_name)]))
304 				continue;
305 
306 			/* First get the entry from the hrDeviceTbl */
307 			entry = map->entry_p;
308 			entry->type = &OIDX_hrDeviceDiskStorage_c;
309 
310 			/* Then check hrDiskStorage table for this device */
311 			disk_entry = disk_find_by_index(entry->index);
312 			if (disk_entry == NULL) {
313 				disk_entry = disk_entry_create(entry);
314 				if (disk_entry == NULL)
315 					continue;
316 
317 				disk_entry->access = DS_READ_WRITE;
318 				strlcpy(disk_entry->dev_name, entry->name,
319 				    sizeof(disk_entry->dev_name));
320 
321 				disk_entry->media = found->media;
322 				disk_entry->removable = found->removable;
323 			}
324 
325 			disk_entry->flags |= HR_DISKSTORAGE_FOUND;
326 			disk_entry->flags |= HR_DISKSTORAGE_ATA;
327 
328 			disk_query_disk(disk_entry);
329 			disk_entry->r_tick = this_tick;
330 		}
331 	}
332 }
333 
334 /**
335  * Find MD disks in the device table.
336  */
337 static void
338 disk_OS_get_MD_disks(void)
339 {
340 	struct device_map_entry *map;
341 	struct device_entry *entry;
342 	struct disk_entry *disk_entry;
343 	struct md_ioctl mdio;
344 	int unit;
345 
346 	if (md_fd <= 0)
347 		return;
348 
349 	/* Look for md devices */
350 	STAILQ_FOREACH(map, &device_map, link) {
351 		/* Skip deleted entries. */
352 		if (map->entry_p == NULL)
353 			continue;
354 		if (sscanf(map->name_key, "md%d", &unit) != 1)
355 			continue;
356 
357 		/* First get the entry from the hrDeviceTbl */
358 		entry = device_find_by_index(map->hrIndex);
359 		entry->type = &OIDX_hrDeviceDiskStorage_c;
360 
361 		/* Then check hrDiskStorage table for this device */
362 		disk_entry = disk_find_by_index(entry->index);
363 		if (disk_entry == NULL) {
364 			disk_entry = disk_entry_create(entry);
365 			if (disk_entry == NULL)
366 				continue;
367 
368 			memset(&mdio, 0, sizeof(mdio));
369 			mdio.md_version = MDIOVERSION;
370 			mdio.md_unit = unit;
371 
372 			if (ioctl(md_fd, MDIOCQUERY, &mdio) < 0) {
373 				syslog(LOG_ERR,
374 				    "hrDiskStorageTable: Couldnt ioctl");
375 				continue;
376 			}
377 
378 			if ((mdio.md_options & MD_READONLY) == MD_READONLY)
379 				disk_entry->access = DS_READ_ONLY;
380 			else
381 				disk_entry->access = DS_READ_WRITE;
382 
383 			strlcpy(disk_entry->dev_name, entry->name,
384 			    sizeof(disk_entry->dev_name));
385 
386 			disk_entry->media = DSM_RAMDISK;
387 			disk_entry->removable = SNMP_FALSE;
388 		}
389 
390 		disk_entry->flags |= HR_DISKSTORAGE_FOUND;
391 		disk_entry->flags |= HR_DISKSTORAGE_MD;
392 		disk_entry->r_tick = this_tick;
393 	}
394 }
395 
396 /**
397  * Find rest of disks
398  */
399 static void
400 disk_OS_get_disks(void)
401 {
402 	size_t disk_cnt = 0;
403 	struct device_entry *entry;
404 	struct disk_entry *disk_entry;
405 
406 	size_t need = 0;
407 
408 	if (sysctlbyname("kern.disks", NULL, &need, NULL, 0) == -1) {
409 		syslog(LOG_ERR, "%s: sysctl_1 kern.disks failed: %m", __func__);
410 		return;
411 	}
412 
413 	if (need == 0)
414 		return;
415 
416 	if (disk_list_len != need + 1 || disk_list == NULL) {
417 		disk_list_len = need + 1;
418 		disk_list = reallocf(disk_list, disk_list_len);
419 	}
420 
421 	if (disk_list == NULL) {
422 		syslog(LOG_ERR, "%s: reallocf failed", __func__);
423 		disk_list_len = 0;
424 		return;
425 	}
426 
427 	memset(disk_list, 0, disk_list_len);
428 
429 	if (sysctlbyname("kern.disks", disk_list, &need, NULL, 0) == -1 ||
430 	    disk_list[0] == 0) {
431 		syslog(LOG_ERR, "%s: sysctl_2 kern.disks failed: %m", __func__);
432 		return;
433 	}
434 
435 	for (disk_cnt = 0; disk_cnt < need; disk_cnt++) {
436 		char *disk = NULL;
437 		char disk_device[128] = "";
438 
439 		disk = strsep(&disk_list, " ");
440 		if (disk == NULL)
441 			break;
442 
443 		snprintf(disk_device, sizeof(disk_device),
444 		    "%s%s", _PATH_DEV, disk);
445 
446 		/* First check if the disk is in the hrDeviceTable. */
447 		if ((entry = device_find_by_name(disk)) == NULL) {
448 			/*
449 			 * not found there - insert it as immutable
450 			 */
451 			syslog(LOG_WARNING, "%s: adding device '%s' to "
452 			    "device list", __func__, disk);
453 
454 			if ((entry = device_entry_create(disk, "", "")) == NULL)
455 				continue;
456 
457 			entry->flags |= HR_DEVICE_IMMUTABLE;
458 		}
459 
460 		entry->type = &OIDX_hrDeviceDiskStorage_c;
461 
462 		/* Then check hrDiskStorage table for this device */
463 		disk_entry = disk_find_by_index(entry->index);
464 		if (disk_entry == NULL) {
465 			disk_entry = disk_entry_create(entry);
466 			if (disk_entry == NULL)
467 				continue;
468 		}
469 
470 		disk_entry->flags |= HR_DISKSTORAGE_FOUND;
471 
472 		if ((disk_entry->flags & HR_DISKSTORAGE_ATA) ||
473 		    (disk_entry->flags & HR_DISKSTORAGE_MD)) {
474 			/*
475 			 * ATA/MD detection is running before this one,
476 			 * so don't waste the time here
477 			 */
478 			continue;
479 		}
480 
481 		disk_entry->access = DS_READ_WRITE;
482 		disk_entry->media = DSM_UNKNOWN;
483 		disk_entry->removable = SNMP_FALSE;
484 
485 		if (strncmp(disk_entry->dev_name, "da", 2) == 0 ||
486 		    strncmp(disk_entry->dev_name, "ada", 3) == 0) {
487 			disk_entry->media = DSM_HARDDISK;
488 			disk_entry->removable = SNMP_FALSE;
489 		} else if (strncmp(disk_entry->dev_name, "cd", 2) == 0) {
490 			disk_entry->media = DSM_OPTICALDISKROM;
491 			disk_entry->removable = SNMP_TRUE;
492 	 	} else {
493 			disk_entry->media = DSM_UNKNOWN;
494 			disk_entry->removable = SNMP_FALSE;
495 		}
496 
497 		strlcpy((char *)disk_entry->dev_name, disk,
498 		    sizeof(disk_entry->dev_name));
499 
500 		disk_query_disk(disk_entry);
501 		disk_entry->r_tick = this_tick;
502 	}
503 }
504 
505 /**
506  * Refresh routine for hrDiskStorageTable
507  * Usable for polling the system for any changes.
508  */
509 void
510 refresh_disk_storage_tbl(int force)
511 {
512 	struct disk_entry *entry, *entry_tmp;
513 
514 	if (disk_storage_tick != 0 && !force &&
515 	    this_tick - disk_storage_tick < disk_storage_tbl_refresh) {
516 		HRDBG("no refresh needed");
517 		return;
518 	}
519 
520 	partition_tbl_pre_refresh();
521 
522 	/* mark each entry as missing */
523 	TAILQ_FOREACH(entry, &disk_tbl, link)
524 		entry->flags &= ~HR_DISKSTORAGE_FOUND;
525 
526 	disk_OS_get_ATA_disks();	/* this must be called first ! */
527 	disk_OS_get_MD_disks();
528 	disk_OS_get_disks();
529 
530 	/*
531 	 * Purge items that disappeared
532 	 */
533 	TAILQ_FOREACH_SAFE(entry, &disk_tbl, link, entry_tmp)
534 		if (!(entry->flags & HR_DISKSTORAGE_FOUND))
535 			/* XXX remove IMMUTABLE entries that have disappeared */
536 			disk_entry_delete(entry);
537 
538 	disk_storage_tick = this_tick;
539 
540 	partition_tbl_post_refresh();
541 
542 	HRDBG("refresh DONE");
543 }
544 
545 /*
546  * Init the things for both of hrDiskStorageTable
547  */
548 int
549 init_disk_storage_tbl(void)
550 {
551 	char mddev[32] = "";
552 
553 	/* Try to load md.ko if not loaded already */
554 	mdmaybeload();
555 
556 	md_fd = -1;
557 	snprintf(mddev, sizeof(mddev) - 1, "%s%s", _PATH_DEV, MDCTL_NAME);
558 	if ((md_fd = open(mddev, O_RDWR)) == -1) {
559 		syslog(LOG_ERR, "open %s failed - will not include md(4) "
560 		    "info: %m", mddev);
561 	}
562 
563 	refresh_disk_storage_tbl(1);
564 
565 	return (0);
566 }
567 
568 /*
569  * Finalization routine for hrDiskStorageTable
570  * It destroys the lists and frees any allocated heap memory
571  */
572 void
573 fini_disk_storage_tbl(void)
574 {
575 	struct disk_entry *n1;
576 
577 	while ((n1 = TAILQ_FIRST(&disk_tbl)) != NULL) {
578 		TAILQ_REMOVE(&disk_tbl, n1, link);
579 		free(n1);
580 	}
581 
582 	free(disk_list);
583 
584 	if (md_fd > 0) {
585 		if (close(md_fd) == -1)
586 			syslog(LOG_ERR,"close (/dev/mdctl) failed: %m");
587 		md_fd = -1;
588 	}
589 }
590 
591 /*
592  * This is the implementation for a generated (by our SNMP "compiler" tool)
593  * function prototype, see hostres_tree.h
594  * It handles the SNMP operations for hrDiskStorageTable
595  */
596 int
597 op_hrDiskStorageTable(struct snmp_context *ctx __unused,
598     struct snmp_value *value, u_int sub, u_int iidx __unused,
599     enum snmp_op curr_op)
600 {
601 	struct disk_entry *entry;
602 
603 	refresh_disk_storage_tbl(0);
604 
605 	switch (curr_op) {
606 
607 	case SNMP_OP_GETNEXT:
608 		if ((entry = NEXT_OBJECT_INT(&disk_tbl,
609 		    &value->var, sub)) == NULL)
610 			return (SNMP_ERR_NOSUCHNAME);
611 		value->var.len = sub + 1;
612 		value->var.subs[sub] = entry->index;
613 		goto get;
614 
615 	case SNMP_OP_GET:
616 		if ((entry = FIND_OBJECT_INT(&disk_tbl,
617 		    &value->var, sub)) == NULL)
618 			return (SNMP_ERR_NOSUCHNAME);
619 		goto get;
620 
621 	case SNMP_OP_SET:
622 		if ((entry = FIND_OBJECT_INT(&disk_tbl,
623 		    &value->var, sub)) == NULL)
624 			return (SNMP_ERR_NO_CREATION);
625 		return (SNMP_ERR_NOT_WRITEABLE);
626 
627 	case SNMP_OP_ROLLBACK:
628 	case SNMP_OP_COMMIT:
629 	  	abort();
630 	}
631 	abort();
632 
633   get:
634 	switch (value->var.subs[sub - 1]) {
635 
636 	case LEAF_hrDiskStorageAccess:
637 	  	value->v.integer = entry->access;
638 	  	return (SNMP_ERR_NOERROR);
639 
640 	case LEAF_hrDiskStorageMedia:
641 	  	value->v.integer = entry->media;
642 	  	return (SNMP_ERR_NOERROR);
643 
644 	case LEAF_hrDiskStorageRemovable:
645 	  	value->v.integer = entry->removable;
646 	  	return (SNMP_ERR_NOERROR);
647 
648 	case LEAF_hrDiskStorageCapacity:
649 	  	value->v.integer = entry->capacity;
650 	  	return (SNMP_ERR_NOERROR);
651 	}
652 	abort();
653 }
654