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
mdmaybeload(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 *
disk_entry_create(const struct device_entry * devEntry)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
disk_entry_delete(struct disk_entry * entry)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 *
disk_find_by_index(int32_t idx)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
disk_query_disk(struct disk_entry * entry)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
disk_OS_get_ATA_disks(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
disk_OS_get_MD_disks(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
disk_OS_get_disks(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
refresh_disk_storage_tbl(int force)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
init_disk_storage_tbl(void)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
fini_disk_storage_tbl(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
op_hrDiskStorageTable(struct snmp_context * ctx __unused,struct snmp_value * value,u_int sub,u_int iidx __unused,enum snmp_op curr_op)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