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