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 */
60enum DeviceStatus {
61	DS_UNKNOWN	= 1,
62	DS_RUNNING	= 2,
63	DS_WARNING	= 3,
64	DS_TESTING	= 4,
65	DS_DOWN		= 5
66};
67
68TAILQ_HEAD(device_tbl, device_entry);
69
70/* the head of the list with hrDeviceTable's entries */
71static struct device_tbl device_tbl = TAILQ_HEAD_INITIALIZER(device_tbl);
72
73/* Table used for consistent device table indexing. */
74struct device_map device_map = STAILQ_HEAD_INITIALIZER(device_map);
75
76/* next int available for indexing the hrDeviceTable */
77static uint32_t next_device_index = 1;
78
79/* last (agent) tick when hrDeviceTable was updated */
80static uint64_t device_tick = 0;
81
82/* maximum number of ticks between updates of device table */
83uint32_t device_tbl_refresh = 10 * 100;
84
85/* socket for /var/run/devd.pipe */
86static int devd_sock = -1;
87
88/* used to wait notifications from /var/run/devd.pipe */
89static void *devd_fd;
90
91/* some constants */
92static const struct asn_oid OIDX_hrDeviceProcessor_c = OIDX_hrDeviceProcessor;
93static const struct asn_oid OIDX_hrDeviceOther_c = OIDX_hrDeviceOther;
94
95/**
96 * Create a new entry out of thin air.
97 */
98struct device_entry *
99device_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 */
243static struct device_entry *
244device_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 */
257void
258device_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 */
278static struct device_entry *
279device_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 */
295struct device_entry *
296device_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 */
309struct device_entry *
310device_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 */
326static void
327device_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 */
346static enum DeviceStatus
347device_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 */
367static int
368device_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 */
401static int
402create_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 */
439static void
440devd_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
449again:
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 */
488void
489init_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 */
502void
503start_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 */
517void
518fini_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 */
549void
550refresh_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 */
619int
620op_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