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
59enum hrDiskStrorageAccess {
60	DS_READ_WRITE = 1,
61	DS_READ_ONLY  = 2
62};
63
64enum 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 */
83struct 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};
101TAILQ_HEAD(disk_tbl, disk_entry);
102
103/* the head of the list with hrDiskStorageTable's entries */
104static struct disk_tbl disk_tbl =
105    TAILQ_HEAD_INITIALIZER(disk_tbl);
106
107/* last tick when hrFSTable was updated */
108static uint64_t disk_storage_tick;
109
110/* minimum number of ticks between refreshs */
111uint32_t disk_storage_tbl_refresh = HR_DISK_TBL_REFRESH * 100;
112
113/* fd for "/dev/mdctl"*/
114static int md_fd = -1;
115
116/* buffer for sysctl("kern.disks") */
117static char *disk_list;
118static size_t disk_list_len;
119
120/* some constants */
121static const struct asn_oid OIDX_hrDeviceDiskStorage_c =
122    OIDX_hrDeviceDiskStorage;
123
124/**
125 * Load the MD driver if it isn't loaded already.
126 */
127static void
128mdmaybeload(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 */
148static struct disk_entry *
149disk_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 */
172static void
173disk_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 */
197static struct disk_entry *
198disk_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 */
212static void
213disk_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 */
249static void
250disk_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 */
337static void
338disk_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 */
399static void
400disk_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 */
509void
510refresh_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 */
548int
549init_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 */
572void
573fini_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 */
596int
597op_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