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: stable/10/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c 325396 2017-11-04 14:56:58Z pfg $
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 */
62enum DeviceStatus {
63	DS_UNKNOWN	= 1,
64	DS_RUNNING	= 2,
65	DS_WARNING	= 3,
66	DS_TESTING	= 4,
67	DS_DOWN		= 5
68};
69
70TAILQ_HEAD(device_tbl, device_entry);
71
72/* the head of the list with hrDeviceTable's entries */
73static struct device_tbl device_tbl = TAILQ_HEAD_INITIALIZER(device_tbl);
74
75/* Table used for consistent device table indexing. */
76struct device_map device_map = STAILQ_HEAD_INITIALIZER(device_map);
77
78/* next int available for indexing the hrDeviceTable */
79static uint32_t next_device_index = 1;
80
81/* last (agent) tick when hrDeviceTable was updated */
82static uint64_t device_tick = 0;
83
84/* maximum number of ticks between updates of device table */
85uint32_t device_tbl_refresh = 10 * 100;
86
87/* socket for /var/run/devd.pipe */
88static int devd_sock = -1;
89
90/* used to wait notifications from /var/run/devd.pipe */
91static void *devd_fd;
92
93/* some constants */
94static const struct asn_oid OIDX_hrDeviceProcessor_c = OIDX_hrDeviceProcessor;
95static const struct asn_oid OIDX_hrDeviceOther_c = OIDX_hrDeviceOther;
96
97/**
98 * Create a new entry out of thin air.
99 */
100struct device_entry *
101device_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 */
245static struct device_entry *
246device_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 */
259void
260device_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 */
280static struct device_entry *
281device_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 */
297struct device_entry *
298device_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 */
311struct device_entry *
312device_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 */
328static void
329device_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 */
348static enum DeviceStatus
349device_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 */
370static int
371device_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 */
404static int
405create_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 */
442static void
443devd_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
452again:
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 */
491void
492init_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 */
505void
506start_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 */
520void
521fini_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 */
552void
553refresh_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 */
622int
623op_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}
687