xref: /netbsd-src/usr.sbin/envstat/envstat.c (revision 53b02e147d4ed531c0d2a5ca9b3e8026ba3e99b5)
1 /* $NetBSD: envstat.c,v 1.101 2021/11/27 22:30:25 rillig Exp $ */
2 
3 /*-
4  * Copyright (c) 2007, 2008 Juan Romero Pardines.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 #ifndef lint
30 __RCSID("$NetBSD: envstat.c,v 1.101 2021/11/27 22:30:25 rillig Exp $");
31 #endif /* not lint */
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <stdbool.h>
36 #include <stdarg.h>
37 #include <stdint.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <err.h>
42 #include <errno.h>
43 #include <paths.h>
44 #include <syslog.h>
45 #include <sys/envsys.h>
46 #include <sys/ioctl.h>
47 #include <sys/types.h>
48 #include <sys/queue.h>
49 #include <prop/proplib.h>
50 
51 #include "envstat.h"
52 #include "prog_ops.h"
53 
54 #define ENVSYS_DFLAG	0x00000001	/* list registered devices */
55 #define ENVSYS_FFLAG	0x00000002	/* show temp in farenheit */
56 #define ENVSYS_LFLAG	0x00000004	/* list sensors */
57 #define ENVSYS_XFLAG	0x00000008	/* externalize dictionary */
58 #define ENVSYS_IFLAG 	0x00000010	/* skip invalid sensors */
59 #define ENVSYS_SFLAG	0x00000020	/* remove all properties set */
60 #define ENVSYS_TFLAG	0x00000040	/* make statistics */
61 #define ENVSYS_NFLAG	0x00000080	/* print value only */
62 #define ENVSYS_KFLAG	0x00000100	/* show temp in kelvin */
63 
64 /* Sensors */
65 typedef struct envsys_sensor {
66 	SIMPLEQ_ENTRY(envsys_sensor) entries;
67 	int32_t	cur_value;
68 	int32_t	max_value;
69 	int32_t	min_value;
70 	int32_t	critmin_value;
71 	int32_t	critmax_value;
72 	int32_t	warnmin_value;
73 	int32_t	warnmax_value;
74 	char	desc[ENVSYS_DESCLEN];
75 	char	type[ENVSYS_DESCLEN];
76 	char	drvstate[ENVSYS_DESCLEN];
77 	char	battcap[ENVSYS_DESCLEN];
78 	char 	dvname[ENVSYS_DESCLEN];
79 	bool	invalid;
80 	bool	visible;
81 	bool	percentage;
82 } *sensor_t;
83 
84 /* Sensor statistics */
85 typedef struct envsys_sensor_stats {
86 	SIMPLEQ_ENTRY(envsys_sensor_stats) entries;
87 	int32_t	max;
88 	int32_t	min;
89 	int32_t avg;
90 	char	desc[ENVSYS_DESCLEN];
91 } *sensor_stats_t;
92 
93 /* Device properties */
94 typedef struct envsys_dvprops {
95 	uint64_t	refresh_timo;
96 	/* more members could be added in the future */
97 } *dvprops_t;
98 
99 /* A simple queue to manage all sensors */
100 static SIMPLEQ_HEAD(, envsys_sensor) sensors_list =
101     SIMPLEQ_HEAD_INITIALIZER(sensors_list);
102 
103 /* A simple queue to manage statistics for all sensors */
104 static SIMPLEQ_HEAD(, envsys_sensor_stats) sensor_stats_list =
105     SIMPLEQ_HEAD_INITIALIZER(sensor_stats_list);
106 
107 static unsigned int 	interval, flags, width;
108 static char 		*mydevname, *sensors;
109 static bool 		statistics;
110 static u_int		header_passes;
111 
112 static int 		parse_dictionary(int);
113 static int		add_sensors(prop_dictionary_t, prop_dictionary_t, const char *, const char *);
114 static int 		send_dictionary(FILE *);
115 static int 		find_sensors(prop_array_t, const char *, dvprops_t);
116 static void 		print_sensors(void);
117 static int 		check_sensors(const char *);
118 static int 		usage(void);
119 
120 static int		sysmonfd; /* fd of /dev/sysmon */
121 
122 int main(int argc, char **argv)
123 {
124 	prop_dictionary_t dict;
125 	int c, rval = 0;
126 	char *endptr, *configfile = NULL;
127 	FILE *cf;
128 
129 	if (prog_init && prog_init() == -1)
130 		err(1, "init failed");
131 
132 	setprogname(argv[0]);
133 
134 	while ((c = getopt(argc, argv, "c:Dd:fIi:klnrSs:Tw:Wx")) != -1) {
135 		switch (c) {
136 		case 'c':	/* configuration file */
137 			configfile = optarg;
138 			break;
139 		case 'D':	/* list registered devices */
140 			flags |= ENVSYS_DFLAG;
141 			break;
142 		case 'd':	/* show sensors of a specific device */
143 			mydevname = optarg;
144 			break;
145 		case 'f':	/* display temperature in Farenheit */
146 			flags |= ENVSYS_FFLAG;
147 			break;
148 		case 'I':	/* Skips invalid sensors */
149 			flags |= ENVSYS_IFLAG;
150 			break;
151 		case 'i':	/* wait time between intervals */
152 			interval = (unsigned int)strtoul(optarg, &endptr, 10);
153 			if (*endptr != '\0')
154 				errx(EXIT_FAILURE, "bad interval '%s'", optarg);
155 			break;
156 		case 'k':	/* display temperature in Kelvin */
157 			flags |= ENVSYS_KFLAG;
158 			break;
159 		case 'l':	/* list sensors */
160 			flags |= ENVSYS_LFLAG;
161 			break;
162 		case 'n':	/* print value only */
163 			flags |= ENVSYS_NFLAG;
164 			break;
165 		case 'r':
166 			/*
167 			 * This flag is noop.. it's only here for
168 			 * compatibility with the old implementation.
169 			 */
170 			break;
171 		case 'S':
172 			flags |= ENVSYS_SFLAG;
173 			break;
174 		case 's':	/* only show specified sensors */
175 			sensors = optarg;
176 			break;
177 		case 'T':	/* make statistics */
178 			flags |= ENVSYS_TFLAG;
179 			break;
180 		case 'w':	/* width value for the lines */
181 			width = (unsigned int)strtoul(optarg, &endptr, 10);
182 			if (*endptr != '\0')
183 				errx(EXIT_FAILURE, "bad width '%s'", optarg);
184 			break;
185 		case 'x':	/* print the dictionary in raw format */
186 			flags |= ENVSYS_XFLAG;
187 			break;
188 		case 'W':	/* No longer used, retained for compatibility */
189 			break;
190 		case '?':
191 		default:
192 			usage();
193 			/* NOTREACHED */
194 		}
195 	}
196 
197 	argc -= optind;
198 	argv += optind;
199 
200 	if (argc > 0 && (flags & ENVSYS_XFLAG) == 0)
201 		usage();
202 
203 	/* Check if we want to make statistics */
204 	if (flags & ENVSYS_TFLAG) {
205 		if (!interval)
206 			errx(EXIT_FAILURE,
207 		    	    "-T cannot be used without an interval (-i)");
208 		else
209 			statistics = true;
210 	}
211 
212 	if (mydevname && sensors)
213 		errx(EXIT_FAILURE, "-d flag cannot be used with -s");
214 
215 	/* Open the device in ro mode */
216 	if ((sysmonfd = prog_open(_PATH_SYSMON, O_RDONLY)) == -1)
217 		err(EXIT_FAILURE, "%s", _PATH_SYSMON);
218 
219 	/* Print dictionary in raw mode */
220 	if (flags & ENVSYS_XFLAG) {
221 		rval = prop_dictionary_recv_ioctl(sysmonfd,
222 						  ENVSYS_GETDICTIONARY,
223 						  &dict);
224 		if (rval)
225 			errx(EXIT_FAILURE, "%s", strerror(rval));
226 
227 		if (mydevname || sensors) {
228 			prop_dictionary_t ndict;
229 
230 			ndict = prop_dictionary_create();
231 			if (ndict == NULL)
232 				err(EXIT_FAILURE, "prop_dictionary_create");
233 
234 			if (mydevname) {
235 				if (add_sensors(ndict, dict, mydevname, NULL))
236 					err(EXIT_FAILURE, "add_sensors");
237 			}
238 			if (sensors) {
239 				char *dvstring, *sstring, *p, *last, *s;
240 				unsigned count = 0;
241 
242 				s = strdup(sensors);
243 				if (s == NULL)
244 					err(EXIT_FAILURE, "strdup");
245 
246 				for ((p = strtok_r(s, ",", &last)); p;
247 				     (p = strtok_r(NULL, ",", &last))) {
248 					/* get device name */
249 					dvstring = strtok(p, ":");
250 					if (dvstring == NULL)
251 						errx(EXIT_FAILURE, "missing device name");
252 
253 					/* get sensor description */
254 					sstring = strtok(NULL, ":");
255 					if (sstring == NULL)
256 						errx(EXIT_FAILURE, "missing sensor description");
257 
258 					if (add_sensors(ndict, dict, dvstring, sstring))
259 						err(EXIT_FAILURE, "add_sensors");
260 
261 					++count;
262 				}
263 				free(s);
264 
265 				/* in case we asked for a single sensor
266 				 * show only the sensor dictionary
267 				 */
268 				if (count == 1) {
269 					prop_object_t obj, obj2;
270 
271 					obj = prop_dictionary_get(ndict, dvstring);
272 					obj2 = prop_array_get(obj, 0);
273 					prop_object_release(ndict);
274 					ndict = obj2;
275 				}
276 			}
277 
278 			prop_object_release(dict);
279 			dict = ndict;
280 		}
281 
282 		if (argc > 0) {
283 			for (; argc > 0; ++argv, --argc)
284 				config_dict_extract(dict, *argv, true);
285 		} else
286 			config_dict_dump(dict);
287 
288 	/* Remove all properties set in dictionary */
289 	} else if (flags & ENVSYS_SFLAG) {
290 		/* Close the ro descriptor */
291 		(void)prog_close(sysmonfd);
292 
293 		/* open the fd in rw mode */
294 		if ((sysmonfd = prog_open(_PATH_SYSMON, O_RDWR)) == -1)
295 			err(EXIT_FAILURE, "%s", _PATH_SYSMON);
296 
297 		dict = prop_dictionary_create();
298 		if (!dict)
299 			err(EXIT_FAILURE, "prop_dictionary_create");
300 
301 		rval = prop_dictionary_set_bool(dict,
302 						"envsys-remove-props",
303 					        true);
304 		if (!rval)
305 			err(EXIT_FAILURE, "prop_dict_set_bool");
306 
307 		/* send the dictionary to the kernel now */
308 		rval = prop_dictionary_send_ioctl(dict, sysmonfd,
309 		    ENVSYS_REMOVEPROPS);
310 		if (rval)
311 			warnx("%s", strerror(rval));
312 
313 	/* Set properties in dictionary */
314 	} else if (configfile) {
315 		/*
316 		 * Parse the configuration file.
317 		 */
318 		if ((cf = fopen(configfile, "r")) == NULL) {
319 			syslog(LOG_ERR, "fopen failed: %s", strerror(errno));
320 			errx(EXIT_FAILURE, "%s", strerror(errno));
321 		}
322 
323 		rval = send_dictionary(cf);
324 		(void)fclose(cf);
325 
326 	/* Show sensors with interval */
327 	} else if (interval) {
328 		for (;;) {
329 			rval = parse_dictionary(sysmonfd);
330 			if (rval)
331 				break;
332 
333 			(void)fflush(stdout);
334 			(void)sleep(interval);
335 		}
336 	/* Show sensors without interval */
337 	} else {
338 		rval = parse_dictionary(sysmonfd);
339 	}
340 
341 	(void)prog_close(sysmonfd);
342 
343 	return rval ? EXIT_FAILURE : EXIT_SUCCESS;
344 }
345 
346 static int
347 send_dictionary(FILE *cf)
348 {
349 	prop_dictionary_t kdict, udict;
350 	int error = 0;
351 
352 	/* Retrieve dictionary from kernel */
353 	error = prop_dictionary_recv_ioctl(sysmonfd,
354 	    ENVSYS_GETDICTIONARY, &kdict);
355       	if (error)
356 		return error;
357 
358 	config_parse(cf, kdict);
359 
360 	/*
361 	 * Dictionary built by the parser from the configuration file.
362 	 */
363 	udict = config_dict_parsed();
364 
365 	/*
366 	 * Close the read only descriptor and open a new one read write.
367 	 */
368 	(void)prog_close(sysmonfd);
369 	if ((sysmonfd = prog_open(_PATH_SYSMON, O_RDWR)) == -1) {
370 		error = errno;
371 		warn("%s", _PATH_SYSMON);
372 		return error;
373 	}
374 
375 	/*
376 	 * Send our sensor properties dictionary to the kernel then.
377 	 */
378 	error = prop_dictionary_send_ioctl(udict,
379 	    sysmonfd, ENVSYS_SETDICTIONARY);
380 	if (error)
381 		warnx("%s", strerror(error));
382 
383 	prop_object_release(udict);
384 	return error;
385 }
386 
387 static sensor_stats_t
388 find_stats_sensor(const char *desc)
389 {
390 	sensor_stats_t stats;
391 
392 	/*
393 	 * If we matched a sensor by its description return it, otherwise
394 	 * allocate a new one.
395 	 */
396 	SIMPLEQ_FOREACH(stats, &sensor_stats_list, entries)
397 		if (strcmp(stats->desc, desc) == 0)
398 			return stats;
399 
400 	stats = calloc(1, sizeof(*stats));
401 	if (stats == NULL)
402 		return NULL;
403 
404 	(void)strlcpy(stats->desc, desc, sizeof(stats->desc));
405 	stats->min = INT32_MAX;
406 	stats->max = INT32_MIN;
407 	SIMPLEQ_INSERT_TAIL(&sensor_stats_list, stats, entries);
408 
409 	return stats;
410 }
411 
412 static int
413 add_sensors(prop_dictionary_t ndict, prop_dictionary_t dict, const char *dev, const char *sensor)
414 {
415 	prop_object_iterator_t iter, iter2;
416 	prop_object_t obj, obj2, desc;
417 	prop_array_t array, narray;
418 	prop_dictionary_t sdict;
419 	const char *dnp;
420 	unsigned int capacity = 1;
421 	uint64_t dummy;
422 	bool found = false;
423 
424 	if (prop_dictionary_count(dict) == 0)
425 		return 0;
426 
427 	narray = prop_dictionary_get(ndict, dev);
428 	if (narray)
429 		found = true;
430 	else {
431 		narray = prop_array_create_with_capacity(capacity);
432 		if (!narray)
433 			return -1;
434 		if (!prop_dictionary_set(ndict, dev, narray)) {
435 			prop_object_release(narray);
436 			return -1;
437 		}
438 	}
439 
440 	iter = prop_dictionary_iterator(dict);
441 	if (iter == NULL)
442 		goto fail;
443 	while ((obj = prop_object_iterator_next(iter)) != NULL) {
444 		array = prop_dictionary_get_keysym(dict, obj);
445 		if (prop_object_type(array) != PROP_TYPE_ARRAY)
446 			break;
447 
448 		dnp = prop_dictionary_keysym_value(obj);
449 		if (strcmp(dev, dnp))
450 			continue;
451 		found = true;
452 
453 		iter2 = prop_array_iterator(array);
454 		while ((obj = prop_object_iterator_next(iter2)) != NULL) {
455 			obj2 = prop_dictionary_get(obj, "device-properties");
456 			if (obj2) {
457 				if (!prop_dictionary_get_uint64(obj2,
458 				    "refresh-timeout", &dummy))
459 					continue;
460 			}
461 
462 			if (sensor) {
463 				desc = prop_dictionary_get(obj, "description");
464 				if (desc == NULL)
465 					continue;
466 
467 				if (!prop_string_equals_string(desc, sensor))
468 					continue;
469 			}
470 
471 			if (!prop_array_ensure_capacity(narray, capacity))
472 				goto fail;
473 
474 			sdict = prop_dictionary_copy(obj);
475 			if (sdict == NULL)
476 				goto fail;
477 			prop_array_add(narray, sdict);
478 			++capacity;
479 		}
480 		prop_object_iterator_release(iter2);
481 	}
482 	prop_object_iterator_release(iter);
483 
484 	/* drop key and array when device wasn't found */
485 	if (!found) {
486 		prop_dictionary_remove(ndict, dev);
487 		prop_object_release(narray);
488 	}
489 
490 	return 0;
491 
492 fail:
493 	prop_dictionary_remove(ndict, dev);
494 	prop_object_release(narray);
495 	return -1;
496 }
497 
498 static int
499 parse_dictionary(int fd)
500 {
501 	sensor_t sensor = NULL;
502 	dvprops_t edp = NULL;
503 	prop_array_t array;
504 	prop_dictionary_t dict;
505 	prop_object_iterator_t iter;
506 	prop_object_t obj;
507 	const char *dnp = NULL;
508 	int rval = 0;
509 
510 	/* receive dictionary from kernel */
511 	rval = prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict);
512 	if (rval)
513 		return rval;
514 
515 	/* No drivers registered? */
516 	if (prop_dictionary_count(dict) == 0) {
517 		warnx("no drivers registered");
518 		goto out;
519 	}
520 
521 	if (mydevname) {
522 		/* -d flag specified, print sensors only for this device */
523 		obj = prop_dictionary_get(dict, mydevname);
524 		if (prop_object_type(obj) != PROP_TYPE_ARRAY) {
525 			warnx("unknown device `%s'", mydevname);
526 			rval = EINVAL;
527 			goto out;
528 		}
529 
530 		rval = find_sensors(obj, mydevname, NULL);
531 		if (rval)
532 			goto out;
533 
534 	} else {
535 		/* print sensors for all devices registered */
536 		iter = prop_dictionary_iterator(dict);
537 		if (iter == NULL) {
538 			rval = EINVAL;
539 			goto out;
540 		}
541 
542 		/* iterate over the dictionary returned by the kernel */
543 		while ((obj = prop_object_iterator_next(iter)) != NULL) {
544 			array = prop_dictionary_get_keysym(dict, obj);
545 			if (prop_object_type(array) != PROP_TYPE_ARRAY) {
546 				warnx("no sensors found");
547 				rval = EINVAL;
548 				goto out;
549 			}
550 
551 			edp = calloc(1, sizeof(*edp));
552 			if (!edp) {
553 				rval = ENOMEM;
554 				goto out;
555 			}
556 
557 			dnp = prop_dictionary_keysym_value(obj);
558 			rval = find_sensors(array, dnp, edp);
559 			if (rval)
560 				goto out;
561 
562 			if (((flags & ENVSYS_LFLAG) == 0) &&
563 			    (flags & ENVSYS_DFLAG)) {
564 				(void)printf("%s (checking events every ",
565 				    dnp);
566 				if (edp->refresh_timo == 1)
567 					(void)printf("second)\n");
568 				else
569 					(void)printf("%d seconds)\n",
570 					    (int)edp->refresh_timo);
571 			}
572 
573 			free(edp);
574 			edp = NULL;
575 		}
576 		prop_object_iterator_release(iter);
577 	}
578 
579 	/* print sensors now */
580 	if (sensors)
581 		rval = check_sensors(sensors);
582 	if ((flags & ENVSYS_LFLAG) == 0 && (flags & ENVSYS_DFLAG) == 0)
583 		print_sensors();
584 	if (interval && ((flags & ENVSYS_NFLAG) == 0 || sensors == NULL))
585 		(void)printf("\n");
586 
587 out:
588 	while ((sensor = SIMPLEQ_FIRST(&sensors_list))) {
589 		SIMPLEQ_REMOVE_HEAD(&sensors_list, entries);
590 		free(sensor);
591 	}
592 	if (edp)
593 		free(edp);
594 	prop_object_release(dict);
595 	return rval;
596 }
597 
598 static int
599 find_sensors(prop_array_t array, const char *dvname, dvprops_t edp)
600 {
601 	prop_object_iterator_t iter;
602 	prop_object_t obj, obj1, obj2;
603 	prop_string_t state, desc = NULL;
604 	sensor_t sensor = NULL;
605 	sensor_stats_t stats = NULL;
606 
607 	iter = prop_array_iterator(array);
608 	if (!iter)
609 		return ENOMEM;
610 
611 	/* iterate over the array of dictionaries */
612 	while ((obj = prop_object_iterator_next(iter)) != NULL) {
613 		/* get the refresh-timeout property */
614 		obj2 = prop_dictionary_get(obj, "device-properties");
615 		if (obj2) {
616 			if (!edp)
617 				continue;
618 			if (!prop_dictionary_get_uint64(obj2,
619 							"refresh-timeout",
620 							&edp->refresh_timo))
621 				continue;
622 		}
623 
624 		/* new sensor coming */
625 		sensor = calloc(1, sizeof(*sensor));
626 		if (sensor == NULL) {
627 			prop_object_iterator_release(iter);
628 			return ENOMEM;
629 		}
630 
631 		/* copy device name */
632 		(void)strlcpy(sensor->dvname, dvname, sizeof(sensor->dvname));
633 
634 		/* description string */
635 		desc = prop_dictionary_get(obj, "description");
636 		if (desc) {
637 			/* copy description */
638 			(void)strlcpy(sensor->desc,
639 			    prop_string_value(desc),
640 		    	    sizeof(sensor->desc));
641 		} else {
642 			free(sensor);
643 			continue;
644 		}
645 
646 		/* type string */
647 		obj1  = prop_dictionary_get(obj, "type");
648 		if (obj1) {
649 			/* copy type */
650 			(void)strlcpy(sensor->type,
651 		    	    prop_string_value(obj1),
652 		    	    sizeof(sensor->type));
653 		} else {
654 			free(sensor);
655 			continue;
656 		}
657 
658 		/* check sensor's state */
659 		state = prop_dictionary_get(obj, "state");
660 
661 		/* mark sensors with invalid/unknown state */
662 		if ((prop_string_equals_string(state, "invalid") ||
663 		     prop_string_equals_string(state, "unknown")))
664 			sensor->invalid = true;
665 
666 		/* get current drive state string */
667 		obj1 = prop_dictionary_get(obj, "drive-state");
668 		if (obj1) {
669 			(void)strlcpy(sensor->drvstate,
670 			    prop_string_value(obj1),
671 			    sizeof(sensor->drvstate));
672 		}
673 
674 		/* get current battery capacity string */
675 		obj1 = prop_dictionary_get(obj, "battery-capacity");
676 		if (obj1) {
677 			(void)strlcpy(sensor->battcap,
678 			    prop_string_value(obj1),
679 			    sizeof(sensor->battcap));
680 		}
681 
682 		/* get current value */
683 		obj1 = prop_dictionary_get(obj, "cur-value");
684 		if (obj1)
685 			sensor->cur_value = prop_number_signed_value(obj1);
686 
687 		/* get max value */
688 		obj1 = prop_dictionary_get(obj, "max-value");
689 		if (obj1)
690 			sensor->max_value = prop_number_signed_value(obj1);
691 
692 		/* get min value */
693 		obj1 = prop_dictionary_get(obj, "min-value");
694 		if (obj1)
695 			sensor->min_value = prop_number_signed_value(obj1);
696 
697 		/* get percentage flag */
698 		obj1 = prop_dictionary_get(obj, "want-percentage");
699 		if (obj1)
700 			sensor->percentage = prop_bool_true(obj1);
701 
702 		/* get critical max value if available */
703 		obj1 = prop_dictionary_get(obj, "critical-max");
704 		if (obj1)
705 			sensor->critmax_value = prop_number_signed_value(obj1);
706 
707 		/* get maximum capacity value if available */
708 		obj1 = prop_dictionary_get(obj, "maximum-capacity");
709 		if (obj1)
710 			sensor->critmax_value = prop_number_signed_value(obj1);
711 
712 		/* get critical min value if available */
713 		obj1 = prop_dictionary_get(obj, "critical-min");
714 		if (obj1)
715 			sensor->critmin_value = prop_number_signed_value(obj1);
716 
717 		/* get critical capacity value if available */
718 		obj1 = prop_dictionary_get(obj, "critical-capacity");
719 		if (obj1)
720 			sensor->critmin_value = prop_number_signed_value(obj1);
721 
722 		/* get warning max value if available */
723 		obj1 = prop_dictionary_get(obj, "warning-max");
724 		if (obj1)
725 			sensor->warnmax_value = prop_number_signed_value(obj1);
726 
727 		/* get high capacity value if available */
728 		obj1 = prop_dictionary_get(obj, "high-capacity");
729 		if (obj1)
730 			sensor->warnmax_value = prop_number_signed_value(obj1);
731 
732 		/* get warning min value if available */
733 		obj1 = prop_dictionary_get(obj, "warning-min");
734 		if (obj1)
735 			sensor->warnmin_value = prop_number_signed_value(obj1);
736 
737 		/* get warning capacity value if available */
738 		obj1 = prop_dictionary_get(obj, "warning-capacity");
739 		if (obj1)
740 			sensor->warnmin_value = prop_number_signed_value(obj1);
741 
742 		/* print sensor names if -l was given */
743 		if (flags & ENVSYS_LFLAG) {
744 			if (width)
745 				(void)printf("%*s\n", width,
746 				    prop_string_value(desc));
747 			else
748 				(void)printf("%s\n",
749 				    prop_string_value(desc));
750 		}
751 
752 		/* Add the sensor into the list */
753 		SIMPLEQ_INSERT_TAIL(&sensors_list, sensor, entries);
754 
755 		/* Collect statistics if flag enabled */
756 		if (statistics) {
757 			/* ignore sensors not relevant for statistics */
758 			if ((strcmp(sensor->type, "Indicator") == 0) ||
759 			    (strcmp(sensor->type, "Battery charge") == 0) ||
760 			    (strcmp(sensor->type, "Drive") == 0))
761 				continue;
762 
763 			/* ignore invalid data */
764 			if (sensor->invalid)
765 				continue;
766 
767 			/* find or allocate a new statistics sensor */
768 			stats = find_stats_sensor(sensor->desc);
769 			if (stats == NULL) {
770 				free(sensor);
771 				prop_object_iterator_release(iter);
772 				return ENOMEM;
773 			}
774 
775 			/* update data */
776 			if (sensor->cur_value > stats->max)
777 				stats->max = sensor->cur_value;
778 
779 			if (sensor->cur_value < stats->min)
780 				stats->min = sensor->cur_value;
781 
782 			/* compute avg value */
783 			stats->avg =
784 			    (sensor->cur_value + stats->max + stats->min) / 3;
785 		}
786 	}
787 
788 	/* free memory */
789 	prop_object_iterator_release(iter);
790 	return 0;
791 }
792 
793 static int
794 check_sensors(const char *str)
795 {
796 	sensor_t sensor = NULL;
797 	char *dvstring, *sstring, *p, *last, *s;
798 	bool sensor_found = false;
799 
800 	if ((s = strdup(str)) == NULL)
801 		return errno;
802 
803 	/*
804 	 * Parse device name and sensor description and find out
805 	 * if the sensor is valid.
806 	 */
807 	for ((p = strtok_r(s, ",", &last)); p;
808 	     (p = strtok_r(NULL, ",", &last))) {
809 		/* get device name */
810 		dvstring = strtok(p, ":");
811 		if (dvstring == NULL) {
812 			warnx("missing device name");
813 			goto out;
814 		}
815 
816 		/* get sensor description */
817 		sstring = strtok(NULL, ":");
818 		if (sstring == NULL) {
819 			warnx("missing sensor description");
820 			goto out;
821 		}
822 
823 		SIMPLEQ_FOREACH(sensor, &sensors_list, entries) {
824 			/* skip until we match device */
825 			if (strcmp(dvstring, sensor->dvname))
826 				continue;
827 			if (strcmp(sstring, sensor->desc) == 0) {
828 				sensor->visible = true;
829 				sensor_found = true;
830 				break;
831 			}
832 		}
833 		if (sensor_found == false) {
834 			warnx("unknown sensor `%s' for device `%s'",
835 		       	    sstring, dvstring);
836 			goto out;
837 		}
838 		sensor_found = false;
839 	}
840 
841 	/* check if all sensors were ok, and error out if not */
842 	SIMPLEQ_FOREACH(sensor, &sensors_list, entries)
843 		if (sensor->visible) {
844 			free(s);
845 			return 0;
846 		}
847 
848 	warnx("no sensors selected to display");
849 out:
850 	free(s);
851 	return EINVAL;
852 }
853 
854 static void
855 print_sensors(void)
856 {
857 	sensor_t sensor;
858 	sensor_stats_t stats = NULL;
859 	size_t maxlen = 0, ilen;
860 	double temp = 0;
861 	const char *invalid = "N/A", *degrees, *tmpstr, *stype;
862 	const char *a, *b, *c, *d, *e, *units;
863 	const char *sep;
864 	int flen;
865 	bool nflag = (flags & ENVSYS_NFLAG) != 0;
866 
867 	tmpstr = stype = d = e = NULL;
868 
869 	/* find the longest description */
870 	SIMPLEQ_FOREACH(sensor, &sensors_list, entries)
871 		if (strlen(sensor->desc) > maxlen)
872 			maxlen = strlen(sensor->desc);
873 
874 	if (width)
875 		maxlen = width;
876 
877 	/*
878 	 * Print a header at the bottom only once showing different
879 	 * members if the statistics flag is set or not.
880 	 *
881 	 * As bonus if -s is set, only print this header every 10 iterations
882 	 * to avoid redundancy... like vmstat(1).
883 	 */
884 
885 	a = "Current";
886 	units = "Unit";
887 	if (statistics) {
888 		b = "Max";
889 		c = "Min";
890 		d = "Avg";
891 	} else {
892 		b = "CritMax";
893 		c = "WarnMax";
894 		d = "WarnMin";
895 		e = "CritMin";
896 	}
897 
898 	if (!nflag) {
899 		if (!sensors || (!header_passes && sensors) ||
900 		    (header_passes == 10 && sensors)) {
901 			if (statistics)
902 				(void)printf("%s%*s  %9s %8s %8s %8s %6s\n",
903 				    mydevname ? "" : "  ", (int)maxlen,
904 				    "", a, b, c, d, units);
905 			else
906 				(void)printf("%s%*s  %9s %8s %8s %8s %8s %5s\n",
907 				    mydevname ? "" : "  ", (int)maxlen,
908 				    "", a, b, c, d, e, units);
909 			if (sensors && header_passes == 10)
910 				header_passes = 0;
911 		}
912 		if (sensors)
913 			header_passes++;
914 
915 		sep = ":";
916 		flen = 10;
917 	} else {
918 		sep = "";
919 		flen = 1;
920 	}
921 
922 	/* print the sensors */
923 	SIMPLEQ_FOREACH(sensor, &sensors_list, entries) {
924 		/* skip sensors that were not marked as visible */
925 		if (sensors && !sensor->visible)
926 			continue;
927 
928 		/* skip invalid sensors if -I is set */
929 		if ((flags & ENVSYS_IFLAG) && sensor->invalid)
930 			continue;
931 
932 		/* print device name */
933 		if (!nflag && !mydevname) {
934 			if (tmpstr == NULL || strcmp(tmpstr, sensor->dvname))
935 				printf("[%s]\n", sensor->dvname);
936 
937 			tmpstr = sensor->dvname;
938 		}
939 
940 		/* find out the statistics sensor */
941 		if (statistics) {
942 			stats = find_stats_sensor(sensor->desc);
943 			if (stats == NULL) {
944 				/* No statistics for this sensor */
945 				continue;
946 			}
947 		}
948 
949 		if (!nflag) {
950 			/* print sensor description */
951 			(void)printf("%s%*.*s", mydevname ? "" : "  ",
952 			    (int)maxlen,
953 			    (int)maxlen, sensor->desc);
954 		}
955 
956 		/* print invalid string */
957 		if (sensor->invalid) {
958 			(void)printf("%s%*s\n", sep, flen, invalid);
959 			continue;
960 		}
961 
962 		/*
963 		 * Indicator and Battery charge sensors.
964 		 */
965 		if ((strcmp(sensor->type, "Indicator") == 0) ||
966 		    (strcmp(sensor->type, "Battery charge") == 0)) {
967 
968 			(void)printf("%s%*s", sep, flen,
969 			     sensor->cur_value ? "TRUE" : "FALSE");
970 
971 /* convert and print a temp value in degC, degF, or Kelvin */
972 #define PRINTTEMP(a)						\
973 do {								\
974 	if (a) {						\
975 		temp = ((a) / 1000000.0);			\
976 		if (flags & ENVSYS_FFLAG) {			\
977 			temp = temp * (9.0 / 5.0) - 459.67;	\
978 			degrees = "degF";			\
979 		} else if (flags & ENVSYS_KFLAG) {		\
980 			degrees = "K";				\
981 		} else {					\
982 			temp = temp - 273.15;			\
983 			degrees = "degC";			\
984 		}						\
985 		(void)printf("%*.3f", (int)ilen, temp);	\
986 		ilen = 9;					\
987 	} else							\
988 		ilen += 9;					\
989 } while (0)
990 
991 		/* temperatures */
992 		} else if (strcmp(sensor->type, "Temperature") == 0) {
993 
994 			ilen = nflag ? 1 : 10;
995 			degrees = "";
996 			(void)printf("%s",sep);
997 			PRINTTEMP(sensor->cur_value);
998 			stype = degrees;
999 
1000 			if (statistics) {
1001 				/* show statistics if flag set */
1002 				PRINTTEMP(stats->max);
1003 				PRINTTEMP(stats->min);
1004 				PRINTTEMP(stats->avg);
1005 				ilen += 2;
1006 			} else if (!nflag) {
1007 				PRINTTEMP(sensor->critmax_value);
1008 				PRINTTEMP(sensor->warnmax_value);
1009 				PRINTTEMP(sensor->warnmin_value);
1010 				PRINTTEMP(sensor->critmin_value);
1011 			}
1012 			if (!nflag)
1013 				(void)printf("%*s", (int)ilen - 3, stype);
1014 #undef PRINTTEMP
1015 
1016 		/* fans */
1017 		} else if (strcmp(sensor->type, "Fan") == 0) {
1018 			stype = "RPM";
1019 
1020 			(void)printf("%s%*u", sep, flen, sensor->cur_value);
1021 
1022 			ilen = 8;
1023 			if (statistics) {
1024 				/* show statistics if flag set */
1025 				(void)printf(" %8u %8u %8u",
1026 				    stats->max, stats->min, stats->avg);
1027 				ilen += 2;
1028 			} else if (!nflag) {
1029 				if (sensor->critmax_value) {
1030 					(void)printf(" %*u", (int)ilen,
1031 					    sensor->critmax_value);
1032 					ilen = 8;
1033 				} else
1034 					ilen += 9;
1035 
1036 				if (sensor->warnmax_value) {
1037 					(void)printf(" %*u", (int)ilen,
1038 					    sensor->warnmax_value);
1039 					ilen = 8;
1040 				} else
1041 					ilen += 9;
1042 
1043 				if (sensor->warnmin_value) {
1044 					(void)printf(" %*u", (int)ilen,
1045 					    sensor->warnmin_value);
1046 					ilen = 8;
1047 				} else
1048 					ilen += 9;
1049 
1050 				if (sensor->critmin_value) {
1051 					(void)printf( " %*u", (int)ilen,
1052 					    sensor->critmin_value);
1053 					ilen = 8;
1054 				} else
1055 					ilen += 9;
1056 
1057 			}
1058 
1059 			if (!nflag)
1060 				(void)printf(" %*s", (int)ilen - 3, stype);
1061 
1062 		/* integers */
1063 		} else if (strcmp(sensor->type, "Integer") == 0) {
1064 
1065 			stype = "none";
1066 
1067 			(void)printf("%s%*d", sep, flen, sensor->cur_value);
1068 
1069 			ilen = 8;
1070 
1071 /* Print percentage of max_value */
1072 #define PRINTPCT(a)							\
1073 do {									\
1074 	if (sensor->max_value) {					\
1075 		(void)printf(" %*.3f%%", (int)ilen,			\
1076 			((a) * 100.0) / sensor->max_value);		\
1077 		ilen = 8;						\
1078 	} else								\
1079 		ilen += 9;						\
1080 } while ( /* CONSTCOND*/ 0 )
1081 
1082 /* Print an integer sensor value */
1083 #define PRINTINT(a)							\
1084 do {									\
1085 	(void)printf(" %*u", (int)ilen, (a));				\
1086 	ilen = 8;							\
1087 } while ( /* CONSTCOND*/ 0 )
1088 
1089 			if (statistics) {
1090 				if (sensor->percentage) {
1091 					PRINTPCT(stats->max);
1092 					PRINTPCT(stats->min);
1093 					PRINTPCT(stats->avg);
1094 				} else {
1095 					PRINTINT(stats->max);
1096 					PRINTINT(stats->min);
1097 					PRINTINT(stats->avg);
1098 				}
1099 				ilen += 2;
1100 			} else if (!nflag) {
1101 				if (sensor->percentage) {
1102 					PRINTPCT(sensor->critmax_value);
1103 					PRINTPCT(sensor->warnmax_value);
1104 					PRINTPCT(sensor->warnmin_value);
1105 					PRINTPCT(sensor->critmin_value);
1106 				} else {
1107 					PRINTINT(sensor->critmax_value);
1108 					PRINTINT(sensor->warnmax_value);
1109 					PRINTINT(sensor->warnmin_value);
1110 					PRINTINT(sensor->critmin_value);
1111 				}
1112 			}
1113 
1114 			if (!nflag)
1115 				(void)printf("%*s", (int)ilen - 3, stype);
1116 
1117 #undef PRINTINT
1118 #undef PRINTPCT
1119 
1120 		/* drives  */
1121 		} else if (strcmp(sensor->type, "Drive") == 0) {
1122 
1123 			(void)printf("%s%*s", sep, flen, sensor->drvstate);
1124 
1125 		/* Battery capacity */
1126 		} else if (strcmp(sensor->type, "Battery capacity") == 0) {
1127 
1128 			(void)printf("%s%*s", sep, flen, sensor->battcap);
1129 
1130 		/* Illuminance */
1131 		} else if (strcmp(sensor->type, "Illuminance") == 0) {
1132 
1133 			stype = "lux";
1134 
1135 			(void)printf("%s%*u", sep, flen, sensor->cur_value);
1136 
1137 			ilen = 8;
1138 			if (statistics) {
1139 				/* show statistics if flag set */
1140 				(void)printf(" %8u %8u %8u",
1141 				    stats->max, stats->min, stats->avg);
1142 				ilen += 2;
1143 			} else if (!nflag) {
1144 				if (sensor->critmax_value) {
1145 					(void)printf(" %*u", (int)ilen,
1146 					    sensor->critmax_value);
1147 					ilen = 8;
1148 				} else
1149 					ilen += 9;
1150 
1151 				if (sensor->warnmax_value) {
1152 					(void)printf(" %*u", (int)ilen,
1153 					    sensor->warnmax_value);
1154 					ilen = 8;
1155 				} else
1156 					ilen += 9;
1157 
1158 				if (sensor->warnmin_value) {
1159 					(void)printf(" %*u", (int)ilen,
1160 					    sensor->warnmin_value);
1161 					ilen = 8;
1162 				} else
1163 					ilen += 9;
1164 
1165 				if (sensor->critmin_value) {
1166 					(void)printf( " %*u", (int)ilen,
1167 					    sensor->critmin_value);
1168 					ilen = 8;
1169 				} else
1170 					ilen += 9;
1171 
1172 			}
1173 
1174 			if (!nflag)
1175 				(void)printf(" %*s", (int)ilen - 3, stype);
1176 
1177 		/* everything else */
1178 		} else {
1179 			if (strcmp(sensor->type, "Voltage DC") == 0)
1180 				stype = "V";
1181 			else if (strcmp(sensor->type, "Voltage AC") == 0)
1182 				stype = "VAC";
1183 			else if (strcmp(sensor->type, "Ampere") == 0)
1184 				stype = "A";
1185 			else if (strcmp(sensor->type, "Watts") == 0)
1186 				stype = "W";
1187 			else if (strcmp(sensor->type, "Ohms") == 0)
1188 				stype = "Ohms";
1189 			else if (strcmp(sensor->type, "Watt hour") == 0)
1190 				stype = "Wh";
1191 			else if (strcmp(sensor->type, "Ampere hour") == 0)
1192 				stype = "Ah";
1193 			else if (strcmp(sensor->type, "relative Humidity") == 0)
1194 				stype = "%rH";
1195 			else
1196 				stype = "?";
1197 
1198 			(void)printf("%s%*.3f", sep, flen,
1199 			    sensor->cur_value / 1000000.0);
1200 
1201 			ilen = 9;
1202 
1203 /* Print percentage of max_value */
1204 #define PRINTPCT(a)							\
1205 do {									\
1206 	if ((a) && sensor->max_value) {					\
1207 		(void)printf("%*.3f%%", (int)ilen,			\
1208 			((a) * 100.0) / sensor->max_value);		\
1209 		ilen = 8;						\
1210 	} else								\
1211 		ilen += 9;						\
1212 } while ( /* CONSTCOND*/ 0 )
1213 
1214 /* Print a generic sensor value */
1215 #define PRINTVAL(a)							\
1216 do {									\
1217 	if ((a)) {							\
1218 		(void)printf("%*.3f", (int)ilen, (a) / 1000000.0);	\
1219 		ilen = 9;						\
1220 	} else								\
1221 		ilen += 9;						\
1222 } while ( /* CONSTCOND*/ 0 )
1223 
1224 			if (statistics) {
1225 				if (sensor->percentage) {
1226 					PRINTPCT(stats->max);
1227 					PRINTPCT(stats->min);
1228 					PRINTPCT(stats->avg);
1229 				} else {
1230 					PRINTVAL(stats->max);
1231 					PRINTVAL(stats->min);
1232 					PRINTVAL(stats->avg);
1233 				}
1234 				ilen += 2;
1235 			} else if (!nflag) {
1236 				if (sensor->percentage) {
1237 					PRINTPCT(sensor->critmax_value);
1238 					PRINTPCT(sensor->warnmax_value);
1239 					PRINTPCT(sensor->warnmin_value);
1240 					PRINTPCT(sensor->critmin_value);
1241 				} else {
1242 
1243 					PRINTVAL(sensor->critmax_value);
1244 					PRINTVAL(sensor->warnmax_value);
1245 					PRINTVAL(sensor->warnmin_value);
1246 					PRINTVAL(sensor->critmin_value);
1247 				}
1248 			}
1249 #undef PRINTPCT
1250 #undef PRINTVAL
1251 
1252 			if (!nflag) {
1253 				(void)printf(" %*s", (int)ilen - 4, stype);
1254 				if (sensor->percentage && sensor->max_value) {
1255 					(void)printf(" (%5.2f%%)",
1256 					    (sensor->cur_value * 100.0) /
1257 					    sensor->max_value);
1258 				}
1259 			}
1260 		}
1261 		(void)printf("\n");
1262 	}
1263 }
1264 
1265 static int
1266 usage(void)
1267 {
1268 	(void)fprintf(stderr, "Usage: %s [-DfIklnrST] ", getprogname());
1269 	(void)fprintf(stderr, "[-c file] [-d device] [-i interval] ");
1270 	(void)fprintf(stderr, "[-s device:sensor,...] [-w width]\n");
1271 	(void)fprintf(stderr, "       %s ", getprogname());
1272 	(void)fprintf(stderr, "[-d device] ");
1273 	(void)fprintf(stderr, "[-s device:sensor,...] ");
1274 	(void)fprintf(stderr, "-x [property]\n");
1275 	exit(EXIT_FAILURE);
1276 	/* NOTREACHED */
1277 }
1278