xref: /freebsd/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
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 *
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 *
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
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 *
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 *
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 *
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
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
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
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
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
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
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
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
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
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
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