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: hrDeviceTable implementation for SNMPd.
34 */
35
36 #include <sys/un.h>
37 #include <sys/limits.h>
38
39 #include <assert.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <syslog.h>
45 #include <unistd.h>
46 #include <sysexits.h>
47
48 #include "hostres_snmp.h"
49 #include "hostres_oid.h"
50 #include "hostres_tree.h"
51
52 #define FREE_DEV_STRUCT(entry_p) do { \
53 free(entry_p->name); \
54 free(entry_p->location); \
55 free(entry_p->descr); \
56 free(entry_p); \
57 } while (0)
58
59 /*
60 * Status of a device
61 */
62 enum DeviceStatus {
63 DS_UNKNOWN = 1,
64 DS_RUNNING = 2,
65 DS_WARNING = 3,
66 DS_TESTING = 4,
67 DS_DOWN = 5
68 };
69
70 TAILQ_HEAD(device_tbl, device_entry);
71
72 /* the head of the list with hrDeviceTable's entries */
73 static struct device_tbl device_tbl = TAILQ_HEAD_INITIALIZER(device_tbl);
74
75 /* Table used for consistent device table indexing. */
76 struct device_map device_map = STAILQ_HEAD_INITIALIZER(device_map);
77
78 /* next int available for indexing the hrDeviceTable */
79 static uint32_t next_device_index = 1;
80
81 /* last (agent) tick when hrDeviceTable was updated */
82 static uint64_t device_tick = 0;
83
84 /* maximum number of ticks between updates of device table */
85 uint32_t device_tbl_refresh = 10 * 100;
86
87 /* socket for /var/run/devd.pipe */
88 static int devd_sock = -1;
89
90 /* used to wait notifications from /var/run/devd.pipe */
91 static void *devd_fd;
92
93 /* some constants */
94 static const struct asn_oid OIDX_hrDeviceProcessor_c = OIDX_hrDeviceProcessor;
95 static const struct asn_oid OIDX_hrDeviceOther_c = OIDX_hrDeviceOther;
96
97 /**
98 * Create a new entry out of thin air.
99 */
100 struct device_entry *
device_entry_create(const char * name,const char * location,const char * descr)101 device_entry_create(const char *name, const char *location, const char *descr)
102 {
103 struct device_entry *entry = NULL;
104 struct device_map_entry *map = NULL;
105 size_t name_len;
106 size_t location_len;
107
108 assert((name[0] != 0) || (location[0] != 0));
109
110 if (name[0] == 0 && location[0] == 0)
111 return (NULL);
112
113 STAILQ_FOREACH(map, &device_map, link) {
114 assert(map->name_key != NULL);
115 assert(map->location_key != NULL);
116
117 if (strcmp(map->name_key, name) == 0 &&
118 strcmp(map->location_key, location) == 0) {
119 break;
120 }
121 }
122
123 if (map == NULL) {
124 /* new object - get a new index */
125 if (next_device_index > INT_MAX) {
126 syslog(LOG_ERR,
127 "%s: hrDeviceTable index wrap", __func__);
128 /* There isn't much we can do here.
129 * If the next_swins_index is consumed
130 * then we can't add entries to this table
131 * So it is better to exit - if the table is sparsed
132 * at the next agent run we can fill it fully.
133 */
134 errx(EX_SOFTWARE, "hrDeviceTable index wrap");
135 /* not reachable */
136 }
137
138 if ((map = malloc(sizeof(*map))) == NULL) {
139 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
140 return (NULL);
141 }
142
143 map->entry_p = NULL;
144
145 name_len = strlen(name) + 1;
146 if (name_len > DEV_NAME_MLEN)
147 name_len = DEV_NAME_MLEN;
148
149 if ((map->name_key = malloc(name_len)) == NULL) {
150 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
151 free(map);
152 return (NULL);
153 }
154
155 location_len = strlen(location) + 1;
156 if (location_len > DEV_LOC_MLEN)
157 location_len = DEV_LOC_MLEN;
158
159 if ((map->location_key = malloc(location_len )) == NULL) {
160 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
161 free(map->name_key);
162 free(map);
163 return (NULL);
164 }
165
166 map->hrIndex = next_device_index++;
167
168 strlcpy(map->name_key, name, name_len);
169 strlcpy(map->location_key, location, location_len);
170
171 STAILQ_INSERT_TAIL(&device_map, map, link);
172 HRDBG("%s at %s added into hrDeviceMap at index=%d",
173 name, location, map->hrIndex);
174 } else {
175 HRDBG("%s at %s exists in hrDeviceMap index=%d",
176 name, location, map->hrIndex);
177 }
178
179 if ((entry = malloc(sizeof(*entry))) == NULL) {
180 syslog(LOG_WARNING, "hrDeviceTable: %s: %m", __func__);
181 return (NULL);
182 }
183 memset(entry, 0, sizeof(*entry));
184
185 entry->index = map->hrIndex;
186 map->entry_p = entry;
187
188 if ((entry->name = strdup(map->name_key)) == NULL) {
189 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
190 free(entry);
191 return (NULL);
192 }
193
194 if ((entry->location = strdup(map->location_key)) == NULL) {
195 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
196 free(entry->name);
197 free(entry);
198 return (NULL);
199 }
200
201 /*
202 * From here till the end of this function we reuse name_len
203 * for a different purpose - for device_entry::descr
204 */
205 if (name[0] != '\0')
206 name_len = strlen(name) + strlen(descr) +
207 strlen(": ") + 1;
208 else
209 name_len = strlen(location) + strlen(descr) +
210 strlen("unknown at : ") + 1;
211
212 if (name_len > DEV_DESCR_MLEN)
213 name_len = DEV_DESCR_MLEN;
214
215 if ((entry->descr = malloc(name_len )) == NULL) {
216 syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
217 free(entry->name);
218 free(entry->location);
219 free(entry);
220 return (NULL);
221 }
222
223 memset(&entry->descr[0], '\0', name_len);
224
225 if (name[0] != '\0')
226 snprintf(entry->descr, name_len,
227 "%s: %s", name, descr);
228 else
229 snprintf(entry->descr, name_len,
230 "unknown at %s: %s", location, descr);
231
232 entry->id = &oid_zeroDotZero; /* unknown id - FIXME */
233 entry->status = (u_int)DS_UNKNOWN;
234 entry->errors = 0;
235 entry->type = &OIDX_hrDeviceOther_c;
236
237 INSERT_OBJECT_INT(entry, &device_tbl);
238
239 return (entry);
240 }
241
242 /**
243 * Create a new entry into the device table.
244 */
245 static struct device_entry *
device_entry_create_devinfo(const struct devinfo_dev * dev_p)246 device_entry_create_devinfo(const struct devinfo_dev *dev_p)
247 {
248
249 assert(dev_p->dd_name != NULL);
250 assert(dev_p->dd_location != NULL);
251
252 return (device_entry_create(dev_p->dd_name, dev_p->dd_location,
253 dev_p->dd_desc));
254 }
255
256 /**
257 * Delete an entry from the device table.
258 */
259 void
device_entry_delete(struct device_entry * entry)260 device_entry_delete(struct device_entry *entry)
261 {
262 struct device_map_entry *map;
263
264 assert(entry != NULL);
265
266 TAILQ_REMOVE(&device_tbl, entry, link);
267
268 STAILQ_FOREACH(map, &device_map, link)
269 if (map->entry_p == entry) {
270 map->entry_p = NULL;
271 break;
272 }
273
274 FREE_DEV_STRUCT(entry);
275 }
276
277 /**
278 * Find an entry given its name and location
279 */
280 static struct device_entry *
device_find_by_dev(const struct devinfo_dev * dev_p)281 device_find_by_dev(const struct devinfo_dev *dev_p)
282 {
283 struct device_map_entry *map;
284
285 assert(dev_p != NULL);
286
287 STAILQ_FOREACH(map, &device_map, link)
288 if (strcmp(map->name_key, dev_p->dd_name) == 0 &&
289 strcmp(map->location_key, dev_p->dd_location) == 0)
290 return (map->entry_p);
291 return (NULL);
292 }
293
294 /**
295 * Find an entry given its index.
296 */
297 struct device_entry *
device_find_by_index(int32_t idx)298 device_find_by_index(int32_t idx)
299 {
300 struct device_entry *entry;
301
302 TAILQ_FOREACH(entry, &device_tbl, link)
303 if (entry->index == idx)
304 return (entry);
305 return (NULL);
306 }
307
308 /**
309 * Find an device entry given its name.
310 */
311 struct device_entry *
device_find_by_name(const char * dev_name)312 device_find_by_name(const char *dev_name)
313 {
314 struct device_map_entry *map;
315
316 assert(dev_name != NULL);
317
318 STAILQ_FOREACH(map, &device_map, link)
319 if (strcmp(map->name_key, dev_name) == 0)
320 return (map->entry_p);
321
322 return (NULL);
323 }
324
325 /**
326 * Find out the type of device. CPU only currently.
327 */
328 static void
device_get_type(struct devinfo_dev * dev_p,const struct asn_oid ** out_type_p)329 device_get_type(struct devinfo_dev *dev_p, const struct asn_oid **out_type_p)
330 {
331
332 assert(dev_p != NULL);
333 assert(out_type_p != NULL);
334
335 if (dev_p == NULL)
336 return;
337
338 if (strncmp(dev_p->dd_name, "cpu", strlen("cpu")) == 0 &&
339 strstr(dev_p->dd_location, ".CPU") != NULL) {
340 *out_type_p = &OIDX_hrDeviceProcessor_c;
341 return;
342 }
343 }
344
345 /**
346 * Get the status of a device
347 */
348 static enum DeviceStatus
device_get_status(struct devinfo_dev * dev)349 device_get_status(struct devinfo_dev *dev)
350 {
351
352 assert(dev != NULL);
353
354 switch (dev->dd_state) {
355 case DS_ALIVE: /* probe succeeded */
356 case DS_NOTPRESENT: /* not probed or probe failed */
357 return (DS_DOWN);
358 case DS_ATTACHED: /* attach method called */
359 case DS_BUSY: /* device is open */
360 return (DS_RUNNING);
361 default:
362 return (DS_UNKNOWN);
363 }
364 }
365
366 /**
367 * Get the info for the given device and then recursively process all
368 * child devices.
369 */
370 static int
device_collector(struct devinfo_dev * dev,void * arg)371 device_collector(struct devinfo_dev *dev, void *arg)
372 {
373 struct device_entry *entry;
374
375 HRDBG("%llu/%llu name='%s' desc='%s' drivername='%s' location='%s'",
376 (unsigned long long)dev->dd_handle,
377 (unsigned long long)dev->dd_parent, dev->dd_name, dev->dd_desc,
378 dev->dd_drivername, dev->dd_location);
379
380 if (dev->dd_name[0] != '\0' || dev->dd_location[0] != '\0') {
381 HRDBG("ANALYZING dev %s at %s",
382 dev->dd_name, dev->dd_location);
383
384 if ((entry = device_find_by_dev(dev)) != NULL) {
385 entry->flags |= HR_DEVICE_FOUND;
386 entry->status = (u_int)device_get_status(dev);
387 } else if ((entry = device_entry_create_devinfo(dev)) != NULL) {
388 device_get_type(dev, &entry->type);
389
390 entry->flags |= HR_DEVICE_FOUND;
391 entry->status = (u_int)device_get_status(dev);
392 }
393 } else {
394 HRDBG("SKIPPED unknown device at location '%s'",
395 dev->dd_location );
396 }
397
398 return (devinfo_foreach_device_child(dev, device_collector, arg));
399 }
400
401 /**
402 * Create the socket to the device daemon.
403 */
404 static int
create_devd_socket(void)405 create_devd_socket(void)
406 {
407 int d_sock;
408 struct sockaddr_un devd_addr;
409
410 bzero(&devd_addr, sizeof(struct sockaddr_un));
411
412 if ((d_sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
413 syslog(LOG_ERR, "Failed to create the socket for %s: %m",
414 PATH_DEVD_PIPE);
415 return (-1);
416 }
417
418 devd_addr.sun_family = PF_LOCAL;
419 devd_addr.sun_len = sizeof(devd_addr);
420 strlcpy(devd_addr.sun_path, PATH_DEVD_PIPE,
421 sizeof(devd_addr.sun_path) - 1);
422
423 if (connect(d_sock, (struct sockaddr *)&devd_addr,
424 sizeof(devd_addr)) == -1) {
425 syslog(LOG_ERR,"Failed to connect socket for %s: %m",
426 PATH_DEVD_PIPE);
427 if (close(d_sock) < 0 )
428 syslog(LOG_ERR,"Failed to close socket for %s: %m",
429 PATH_DEVD_PIPE);
430 return (-1);
431 }
432
433 return (d_sock);
434 }
435
436 /*
437 * Event on the devd socket.
438 *
439 * We should probably directly process entries here. For simplicity just
440 * call the refresh routine with the force flag for now.
441 */
442 static void
devd_socket_callback(int fd,void * arg __unused)443 devd_socket_callback(int fd, void *arg __unused)
444 {
445 char buf[512];
446 int read_len = -1;
447
448 assert(fd == devd_sock);
449
450 HRDBG("called");
451
452 again:
453 read_len = read(fd, buf, sizeof(buf));
454 if (read_len < 0) {
455 if (errno == EBADF) {
456 devd_sock = -1;
457 if (devd_fd != NULL) {
458 fd_deselect(devd_fd);
459 devd_fd = NULL;
460 }
461 syslog(LOG_ERR, "Closing devd_fd, revert to "
462 "devinfo polling");
463 }
464
465 } else if (read_len == 0) {
466 syslog(LOG_ERR, "zero bytes read from devd pipe... "
467 "closing socket!");
468
469 if (close(devd_sock) < 0 )
470 syslog(LOG_ERR, "Failed to close devd socket: %m");
471
472 devd_sock = -1;
473 if (devd_fd != NULL) {
474 fd_deselect(devd_fd);
475 devd_fd = NULL;
476 }
477 syslog(LOG_ERR, "Closing devd_fd, revert to devinfo polling");
478
479 } else {
480 if (read_len == sizeof(buf))
481 goto again;
482 refresh_device_tbl(1);
483 }
484 }
485
486 /**
487 * Initialize and populate the device table.
488 */
489 void
init_device_tbl(void)490 init_device_tbl(void)
491 {
492
493 /* initially populate table for the other tables */
494 refresh_device_tbl(1);
495
496 /* no problem if that fails - just use polling mode */
497 devd_sock = create_devd_socket();
498 }
499
500 /**
501 * Start devd(8) monitoring.
502 */
503 void
start_device_tbl(struct lmodule * mod)504 start_device_tbl(struct lmodule *mod)
505 {
506
507 if (devd_sock > 0) {
508 devd_fd = fd_select(devd_sock, devd_socket_callback, NULL, mod);
509 if (devd_fd == NULL)
510 syslog(LOG_ERR, "fd_select failed on devd socket: %m");
511 }
512 }
513
514 /**
515 * Finalization routine for hrDeviceTable
516 * It destroys the lists and frees any allocated heap memory
517 */
518 void
fini_device_tbl(void)519 fini_device_tbl(void)
520 {
521 struct device_map_entry *n1;
522
523 if (devd_fd != NULL)
524 fd_deselect(devd_fd);
525
526 if (devd_sock != -1)
527 (void)close(devd_sock);
528
529 devinfo_free();
530
531 while ((n1 = STAILQ_FIRST(&device_map)) != NULL) {
532 STAILQ_REMOVE_HEAD(&device_map, link);
533 if (n1->entry_p != NULL) {
534 TAILQ_REMOVE(&device_tbl, n1->entry_p, link);
535 FREE_DEV_STRUCT(n1->entry_p);
536 }
537 free(n1->name_key);
538 free(n1->location_key);
539 free(n1);
540 }
541 assert(TAILQ_EMPTY(&device_tbl));
542 }
543
544 /**
545 * Refresh routine for hrDeviceTable. We don't refresh here if the devd socket
546 * is open, because in this case we have the actual information always. We
547 * also don't refresh when the table is new enough (if we don't have a devd
548 * socket). In either case a refresh can be forced by passing a non-zero value.
549 */
550 void
refresh_device_tbl(int force)551 refresh_device_tbl(int force)
552 {
553 struct device_entry *entry, *entry_tmp;
554 struct devinfo_dev *dev_root;
555 static int act = 0;
556
557 if (!force && (devd_sock >= 0 ||
558 (device_tick != 0 && this_tick - device_tick < device_tbl_refresh))){
559 HRDBG("no refresh needed");
560 return;
561 }
562
563 if (act) {
564 syslog(LOG_ERR, "%s: recursive call", __func__);
565 return;
566 }
567
568 if (devinfo_init() != 0) {
569 syslog(LOG_ERR,"%s: devinfo_init failed: %m", __func__);
570 return;
571 }
572
573 act = 1;
574 if ((dev_root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL){
575 syslog(LOG_ERR, "%s: can't get the root device: %m", __func__);
576 goto out;
577 }
578
579 /* mark each entry as missing */
580 TAILQ_FOREACH(entry, &device_tbl, link)
581 entry->flags &= ~HR_DEVICE_FOUND;
582
583 if (devinfo_foreach_device_child(dev_root, device_collector, NULL))
584 syslog(LOG_ERR, "%s: devinfo_foreach_device_child failed",
585 __func__);
586
587 /*
588 * Purge items that disappeared
589 */
590 TAILQ_FOREACH_SAFE(entry, &device_tbl, link, entry_tmp) {
591 /*
592 * If HR_DEVICE_IMMUTABLE bit is set then this means that
593 * this entry was not detected by the above
594 * devinfo_foreach_device() call. So we are not deleting
595 * it there.
596 */
597 if (!(entry->flags & HR_DEVICE_FOUND) &&
598 !(entry->flags & HR_DEVICE_IMMUTABLE))
599 device_entry_delete(entry);
600 }
601
602 device_tick = this_tick;
603
604 /*
605 * Force a refresh for the hrDiskStorageTable
606 * XXX Why not the other dependen tables?
607 */
608 refresh_disk_storage_tbl(1);
609
610 out:
611 devinfo_free();
612 act = 0;
613 }
614
615 /**
616 * This is the implementation for a generated (by a SNMP tool)
617 * function prototype, see hostres_tree.h
618 * It handles the SNMP operations for hrDeviceTable
619 */
620 int
op_hrDeviceTable(struct snmp_context * ctx __unused,struct snmp_value * value,u_int sub,u_int iidx __unused,enum snmp_op curr_op)621 op_hrDeviceTable(struct snmp_context *ctx __unused, struct snmp_value *value,
622 u_int sub, u_int iidx __unused, enum snmp_op curr_op)
623 {
624 struct device_entry *entry;
625
626 refresh_device_tbl(0);
627
628 switch (curr_op) {
629
630 case SNMP_OP_GETNEXT:
631 if ((entry = NEXT_OBJECT_INT(&device_tbl,
632 &value->var, sub)) == NULL)
633 return (SNMP_ERR_NOSUCHNAME);
634 value->var.len = sub + 1;
635 value->var.subs[sub] = entry->index;
636 goto get;
637
638 case SNMP_OP_GET:
639 if ((entry = FIND_OBJECT_INT(&device_tbl,
640 &value->var, sub)) == NULL)
641 return (SNMP_ERR_NOSUCHNAME);
642 goto get;
643
644 case SNMP_OP_SET:
645 if ((entry = FIND_OBJECT_INT(&device_tbl,
646 &value->var, sub)) == NULL)
647 return (SNMP_ERR_NO_CREATION);
648 return (SNMP_ERR_NOT_WRITEABLE);
649
650 case SNMP_OP_ROLLBACK:
651 case SNMP_OP_COMMIT:
652 abort();
653 }
654 abort();
655
656 get:
657 switch (value->var.subs[sub - 1]) {
658
659 case LEAF_hrDeviceIndex:
660 value->v.integer = entry->index;
661 return (SNMP_ERR_NOERROR);
662
663 case LEAF_hrDeviceType:
664 assert(entry->type != NULL);
665 value->v.oid = *(entry->type);
666 return (SNMP_ERR_NOERROR);
667
668 case LEAF_hrDeviceDescr:
669 return (string_get(value, entry->descr, -1));
670
671 case LEAF_hrDeviceID:
672 value->v.oid = *(entry->id);
673 return (SNMP_ERR_NOERROR);
674
675 case LEAF_hrDeviceStatus:
676 value->v.integer = entry->status;
677 return (SNMP_ERR_NOERROR);
678
679 case LEAF_hrDeviceErrors:
680 value->v.uint32 = entry->errors;
681 return (SNMP_ERR_NOERROR);
682 }
683 abort();
684 }
685