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