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 		return (DS_RUNNING);
360 	default:
361 		return (DS_UNKNOWN);
362 	}
363 }
364 
365 /**
366  * Get the info for the given device and then recursively process all
367  * child devices.
368  */
369 static int
370 device_collector(struct devinfo_dev *dev, void *arg)
371 {
372 	struct device_entry *entry;
373 
374 	HRDBG("%llu/%llu name='%s' desc='%s' drivername='%s' location='%s'",
375 	    (unsigned long long)dev->dd_handle,
376 	    (unsigned long long)dev->dd_parent, dev->dd_name, dev->dd_desc,
377 	    dev->dd_drivername, dev->dd_location);
378 
379 	if (dev->dd_name[0] != '\0' || dev->dd_location[0] != '\0') {
380 		HRDBG("ANALYZING dev %s at %s",
381 		    dev->dd_name, dev->dd_location);
382 
383 		if ((entry = device_find_by_dev(dev)) != NULL) {
384 			entry->flags |= HR_DEVICE_FOUND;
385 			entry->status = (u_int)device_get_status(dev);
386 		} else if ((entry = device_entry_create_devinfo(dev)) != NULL) {
387 			device_get_type(dev, &entry->type);
388 
389 			entry->flags |= HR_DEVICE_FOUND;
390 			entry->status = (u_int)device_get_status(dev);
391 		}
392 	} else {
393 		HRDBG("SKIPPED unknown device at location '%s'",
394 		    dev->dd_location );
395 	}
396 
397 	return (devinfo_foreach_device_child(dev, device_collector, arg));
398 }
399 
400 /**
401  * Create the socket to the device daemon.
402  */
403 static int
404 create_devd_socket(void)
405 {
406 	int d_sock;
407  	struct sockaddr_un devd_addr;
408 
409  	bzero(&devd_addr, sizeof(struct sockaddr_un));
410 
411  	if ((d_sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
412  		syslog(LOG_ERR, "Failed to create the socket for %s: %m",
413 		    PATH_DEVD_PIPE);
414  		return (-1);
415  	}
416 
417  	devd_addr.sun_family = PF_LOCAL;
418 	devd_addr.sun_len = sizeof(devd_addr);
419  	strlcpy(devd_addr.sun_path, PATH_DEVD_PIPE,
420 	    sizeof(devd_addr.sun_path) - 1);
421 
422  	if (connect(d_sock, (struct sockaddr *)&devd_addr,
423 	    sizeof(devd_addr)) == -1) {
424  		syslog(LOG_ERR,"Failed to connect socket for %s: %m",
425 		    PATH_DEVD_PIPE);
426  		if (close(d_sock) < 0 )
427  			syslog(LOG_ERR,"Failed to close socket for %s: %m",
428 			    PATH_DEVD_PIPE);
429 		return (-1);
430  	}
431 
432  	return (d_sock);
433 }
434 
435 /*
436  * Event on the devd socket.
437  *
438  * We should probably directly process entries here. For simplicity just
439  * call the refresh routine with the force flag for now.
440  */
441 static void
442 devd_socket_callback(int fd, void *arg __unused)
443 {
444 	char buf[512];
445 	int read_len = -1;
446 
447 	assert(fd == devd_sock);
448 
449 	HRDBG("called");
450 
451 again:
452 	read_len = read(fd, buf, sizeof(buf));
453 	if (read_len < 0) {
454 		if (errno == EBADF) {
455 			devd_sock = -1;
456 			if (devd_fd != NULL) {
457 				fd_deselect(devd_fd);
458 				devd_fd = NULL;
459 			}
460 			syslog(LOG_ERR, "Closing devd_fd, revert to "
461 			    "devinfo polling");
462 		}
463 
464 	} else if (read_len == 0) {
465 		syslog(LOG_ERR, "zero bytes read from devd pipe... "
466 		    "closing socket!");
467 
468 		if (close(devd_sock) < 0 )
469  			syslog(LOG_ERR, "Failed to close devd socket: %m");
470 
471 		devd_sock = -1;
472 		if (devd_fd != NULL) {
473 			fd_deselect(devd_fd);
474 			devd_fd = NULL;
475 		}
476 		syslog(LOG_ERR, "Closing devd_fd, revert to devinfo polling");
477 
478 	} else {
479 		if (read_len == sizeof(buf))
480 			goto again;
481 		/* Only refresh device table on a device add or remove event. */
482 		if (buf[0] == '+' || buf[0] == '-')
483 			refresh_device_tbl(1);
484 	}
485 }
486 
487 /**
488  * Initialize and populate the device table.
489  */
490 void
491 init_device_tbl(void)
492 {
493 
494 	/* initially populate table for the other tables */
495 	refresh_device_tbl(1);
496 
497 	/* no problem if that fails - just use polling mode */
498 	devd_sock = create_devd_socket();
499 }
500 
501 /**
502  * Start devd(8) monitoring.
503  */
504 void
505 start_device_tbl(struct lmodule *mod)
506 {
507 
508 	if (devd_sock > 0) {
509 		devd_fd = fd_select(devd_sock, devd_socket_callback, NULL, mod);
510 		if (devd_fd == NULL)
511 			syslog(LOG_ERR, "fd_select failed on devd socket: %m");
512 	}
513 }
514 
515 /**
516  * Finalization routine for hrDeviceTable
517  * It destroys the lists and frees any allocated heap memory
518  */
519 void
520 fini_device_tbl(void)
521 {
522 	struct device_map_entry *n1;
523 
524 	if (devd_fd != NULL)
525 		fd_deselect(devd_fd);
526 
527 	if (devd_sock != -1)
528 		(void)close(devd_sock);
529 
530 	devinfo_free();
531 
532      	while ((n1 = STAILQ_FIRST(&device_map)) != NULL) {
533 		STAILQ_REMOVE_HEAD(&device_map, link);
534 		if (n1->entry_p != NULL) {
535 			TAILQ_REMOVE(&device_tbl, n1->entry_p, link);
536 			FREE_DEV_STRUCT(n1->entry_p);
537 		}
538 		free(n1->name_key);
539 		free(n1->location_key);
540 		free(n1);
541      	}
542 	assert(TAILQ_EMPTY(&device_tbl));
543 }
544 
545 /**
546  * Refresh routine for hrDeviceTable. We don't refresh here if the devd socket
547  * is open, because in this case we have the actual information always. We
548  * also don't refresh when the table is new enough (if we don't have a devd
549  * socket). In either case a refresh can be forced by passing a non-zero value.
550  */
551 void
552 refresh_device_tbl(int force)
553 {
554 	struct device_entry *entry, *entry_tmp;
555 	struct devinfo_dev *dev_root;
556 	static int act = 0;
557 
558 	if (!force && (devd_sock >= 0 ||
559 	   (device_tick != 0 && this_tick - device_tick < device_tbl_refresh))){
560 		HRDBG("no refresh needed");
561 		return;
562 	}
563 
564 	if (act) {
565 		syslog(LOG_ERR, "%s: recursive call", __func__);
566 		return;
567 	}
568 
569 	if (devinfo_init() != 0) {
570 		syslog(LOG_ERR,"%s: devinfo_init failed: %m", __func__);
571 		return;
572 	}
573 
574 	act = 1;
575 	if ((dev_root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL){
576 		syslog(LOG_ERR, "%s: can't get the root device: %m", __func__);
577 		goto out;
578 	}
579 
580 	/* mark each entry as missing */
581 	TAILQ_FOREACH(entry, &device_tbl, link)
582 		entry->flags &= ~HR_DEVICE_FOUND;
583 
584 	if (devinfo_foreach_device_child(dev_root, device_collector, NULL))
585 		syslog(LOG_ERR, "%s: devinfo_foreach_device_child failed",
586 		    __func__);
587 
588 	/*
589 	 * Purge items that disappeared
590 	 */
591 	TAILQ_FOREACH_SAFE(entry, &device_tbl, link, entry_tmp) {
592 		/*
593 		 * If HR_DEVICE_IMMUTABLE bit is set then this means that
594 		 * this entry was not detected by the above
595 		 * devinfo_foreach_device() call. So we are not deleting
596 		 * it there.
597 		 */
598 		if (!(entry->flags & HR_DEVICE_FOUND) &&
599 		    !(entry->flags & HR_DEVICE_IMMUTABLE))
600 			device_entry_delete(entry);
601 	}
602 
603 	device_tick = this_tick;
604 
605 	/*
606 	 * Force a refresh for the hrDiskStorageTable
607 	 * XXX Why not the other dependen tables?
608 	 */
609 	refresh_disk_storage_tbl(1);
610 
611   out:
612 	devinfo_free();
613 	act = 0;
614 }
615 
616 /**
617  * This is the implementation for a generated (by a SNMP tool)
618  * function prototype, see hostres_tree.h
619  * It handles the SNMP operations for hrDeviceTable
620  */
621 int
622 op_hrDeviceTable(struct snmp_context *ctx __unused, struct snmp_value *value,
623     u_int sub, u_int iidx __unused, enum snmp_op curr_op)
624 {
625 	struct device_entry *entry;
626 
627 	refresh_device_tbl(0);
628 
629 	switch (curr_op) {
630 
631 	case SNMP_OP_GETNEXT:
632 		if ((entry = NEXT_OBJECT_INT(&device_tbl,
633 		    &value->var, sub)) == NULL)
634 			return (SNMP_ERR_NOSUCHNAME);
635 		value->var.len = sub + 1;
636 		value->var.subs[sub] = entry->index;
637 		goto get;
638 
639 	case SNMP_OP_GET:
640 		if ((entry = FIND_OBJECT_INT(&device_tbl,
641 		    &value->var, sub)) == NULL)
642 			return (SNMP_ERR_NOSUCHNAME);
643 		goto get;
644 
645 	case SNMP_OP_SET:
646 		if ((entry = FIND_OBJECT_INT(&device_tbl,
647 		    &value->var, sub)) == NULL)
648 			return (SNMP_ERR_NO_CREATION);
649 		return (SNMP_ERR_NOT_WRITEABLE);
650 
651 	case SNMP_OP_ROLLBACK:
652 	case SNMP_OP_COMMIT:
653 		abort();
654 	}
655 	abort();
656 
657   get:
658 	switch (value->var.subs[sub - 1]) {
659 
660 	case LEAF_hrDeviceIndex:
661 		value->v.integer = entry->index;
662 		return (SNMP_ERR_NOERROR);
663 
664 	case LEAF_hrDeviceType:
665 		assert(entry->type != NULL);
666 	  	value->v.oid = *(entry->type);
667 	  	return (SNMP_ERR_NOERROR);
668 
669 	case LEAF_hrDeviceDescr:
670 	  	return (string_get(value, entry->descr, -1));
671 
672 	case LEAF_hrDeviceID:
673 		value->v.oid = *(entry->id);
674 	  	return (SNMP_ERR_NOERROR);
675 
676 	case LEAF_hrDeviceStatus:
677 	  	value->v.integer = entry->status;
678 	  	return (SNMP_ERR_NOERROR);
679 
680 	case LEAF_hrDeviceErrors:
681 	  	value->v.uint32 = entry->errors;
682 	  	return (SNMP_ERR_NOERROR);
683 	}
684 	abort();
685 }
686