]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c
MFC r325067:
[FreeBSD/stable/10.git] / usr.sbin / bsnmpd / modules / snmp_hostres / hostres_device_tbl.c
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 *
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 *
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
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 *
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 *
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 *
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
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
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
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
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
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                 /* Only refresh device table on a device add or remove event. */
483                 if (buf[0] == '+' || buf[0] == '-')
484                         refresh_device_tbl(1);
485         }
486 }
487
488 /**
489  * Initialize and populate the device table.
490  */
491 void
492 init_device_tbl(void)
493 {
494
495         /* initially populate table for the other tables */
496         refresh_device_tbl(1);
497
498         /* no problem if that fails - just use polling mode */
499         devd_sock = create_devd_socket();
500 }
501
502 /**
503  * Start devd(8) monitoring.
504  */
505 void
506 start_device_tbl(struct lmodule *mod)
507 {
508
509         if (devd_sock > 0) {
510                 devd_fd = fd_select(devd_sock, devd_socket_callback, NULL, mod);
511                 if (devd_fd == NULL)
512                         syslog(LOG_ERR, "fd_select failed on devd socket: %m");
513         }
514 }
515
516 /**
517  * Finalization routine for hrDeviceTable
518  * It destroys the lists and frees any allocated heap memory
519  */
520 void
521 fini_device_tbl(void)
522 {
523         struct device_map_entry *n1;
524
525         if (devd_fd != NULL)
526                 fd_deselect(devd_fd);
527
528         if (devd_sock != -1)
529                 (void)close(devd_sock);
530
531         devinfo_free();
532
533         while ((n1 = STAILQ_FIRST(&device_map)) != NULL) {
534                 STAILQ_REMOVE_HEAD(&device_map, link);
535                 if (n1->entry_p != NULL) {
536                         TAILQ_REMOVE(&device_tbl, n1->entry_p, link);
537                         FREE_DEV_STRUCT(n1->entry_p);
538                 }
539                 free(n1->name_key);
540                 free(n1->location_key);
541                 free(n1);
542         }
543         assert(TAILQ_EMPTY(&device_tbl));
544 }
545
546 /**
547  * Refresh routine for hrDeviceTable. We don't refresh here if the devd socket
548  * is open, because in this case we have the actual information always. We
549  * also don't refresh when the table is new enough (if we don't have a devd
550  * socket). In either case a refresh can be forced by passing a non-zero value.
551  */
552 void
553 refresh_device_tbl(int force)
554 {
555         struct device_entry *entry, *entry_tmp;
556         struct devinfo_dev *dev_root;
557         static int act = 0;
558
559         if (!force && (devd_sock >= 0 ||
560            (device_tick != 0 && this_tick - device_tick < device_tbl_refresh))){
561                 HRDBG("no refresh needed");
562                 return;
563         }
564
565         if (act) {
566                 syslog(LOG_ERR, "%s: recursive call", __func__);
567                 return;
568         }
569
570         if (devinfo_init() != 0) {
571                 syslog(LOG_ERR,"%s: devinfo_init failed: %m", __func__);
572                 return;
573         }
574
575         act = 1;
576         if ((dev_root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL){
577                 syslog(LOG_ERR, "%s: can't get the root device: %m", __func__);
578                 goto out;
579         }
580
581         /* mark each entry as missing */
582         TAILQ_FOREACH(entry, &device_tbl, link)
583                 entry->flags &= ~HR_DEVICE_FOUND;
584
585         if (devinfo_foreach_device_child(dev_root, device_collector, NULL))
586                 syslog(LOG_ERR, "%s: devinfo_foreach_device_child failed",
587                     __func__);
588
589         /*
590          * Purge items that disappeared
591          */
592         TAILQ_FOREACH_SAFE(entry, &device_tbl, link, entry_tmp) {
593                 /*
594                  * If HR_DEVICE_IMMUTABLE bit is set then this means that
595                  * this entry was not detected by the above
596                  * devinfo_foreach_device() call. So we are not deleting
597                  * it there.
598                  */
599                 if (!(entry->flags & HR_DEVICE_FOUND) &&
600                     !(entry->flags & HR_DEVICE_IMMUTABLE))
601                         device_entry_delete(entry);
602         }
603
604         device_tick = this_tick;
605
606         /*
607          * Force a refresh for the hrDiskStorageTable
608          * XXX Why not the other dependen tables?
609          */
610         refresh_disk_storage_tbl(1);
611
612   out:
613         devinfo_free();
614         act = 0;
615 }
616
617 /**
618  * This is the implementation for a generated (by a SNMP tool)
619  * function prototype, see hostres_tree.h
620  * It handles the SNMP operations for hrDeviceTable
621  */
622 int
623 op_hrDeviceTable(struct snmp_context *ctx __unused, struct snmp_value *value,
624     u_int sub, u_int iidx __unused, enum snmp_op curr_op)
625 {
626         struct device_entry *entry;
627
628         refresh_device_tbl(0);
629
630         switch (curr_op) {
631
632         case SNMP_OP_GETNEXT:
633                 if ((entry = NEXT_OBJECT_INT(&device_tbl,
634                     &value->var, sub)) == NULL)
635                         return (SNMP_ERR_NOSUCHNAME);
636                 value->var.len = sub + 1;
637                 value->var.subs[sub] = entry->index;
638                 goto get;
639
640         case SNMP_OP_GET:
641                 if ((entry = FIND_OBJECT_INT(&device_tbl,
642                     &value->var, sub)) == NULL)
643                         return (SNMP_ERR_NOSUCHNAME);
644                 goto get;
645
646         case SNMP_OP_SET:
647                 if ((entry = FIND_OBJECT_INT(&device_tbl,
648                     &value->var, sub)) == NULL)
649                         return (SNMP_ERR_NO_CREATION);
650                 return (SNMP_ERR_NOT_WRITEABLE);
651
652         case SNMP_OP_ROLLBACK:
653         case SNMP_OP_COMMIT:
654                 abort();
655         }
656         abort();
657
658   get:
659         switch (value->var.subs[sub - 1]) {
660
661         case LEAF_hrDeviceIndex:
662                 value->v.integer = entry->index;
663                 return (SNMP_ERR_NOERROR);
664
665         case LEAF_hrDeviceType:
666                 assert(entry->type != NULL);
667                 value->v.oid = *(entry->type);
668                 return (SNMP_ERR_NOERROR);
669
670         case LEAF_hrDeviceDescr:
671                 return (string_get(value, entry->descr, -1));
672
673         case LEAF_hrDeviceID:
674                 value->v.oid = *(entry->id);
675                 return (SNMP_ERR_NOERROR);
676
677         case LEAF_hrDeviceStatus:
678                 value->v.integer = entry->status;
679                 return (SNMP_ERR_NOERROR);
680
681         case LEAF_hrDeviceErrors:
682                 value->v.uint32 = entry->errors;
683                 return (SNMP_ERR_NOERROR);
684         }
685         abort();
686 }