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