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