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
57enum hrDiskStrorageAccess {
58	DS_READ_WRITE = 1,
59	DS_READ_ONLY  = 2
60};
61
62enum 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 */
81struct 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};
99TAILQ_HEAD(disk_tbl, disk_entry);
100
101/* the head of the list with hrDiskStorageTable's entries */
102static struct disk_tbl disk_tbl =
103    TAILQ_HEAD_INITIALIZER(disk_tbl);
104
105/* last tick when hrFSTable was updated */
106static uint64_t disk_storage_tick;
107
108/* minimum number of ticks between refreshs */
109uint32_t disk_storage_tbl_refresh = HR_DISK_TBL_REFRESH * 100;
110
111/* fd for "/dev/mdctl"*/
112static int md_fd = -1;
113
114/* buffer for sysctl("kern.disks") */
115static char *disk_list;
116static size_t disk_list_len;
117
118/* some constants */
119static const struct asn_oid OIDX_hrDeviceDiskStorage_c =
120    OIDX_hrDeviceDiskStorage;
121
122/**
123 * Load the MD driver if it isn't loaded already.
124 */
125static void
126mdmaybeload(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 */
146static struct disk_entry *
147disk_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 */
170static void
171disk_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 */
195static struct disk_entry *
196disk_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 */
210static void
211disk_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 */
247static void
248disk_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 */
335static void
336disk_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 */
397static void
398disk_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 */
507void
508refresh_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 */
546int
547init_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 */
570void
571fini_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 */
594int
595op_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