xref: /netbsd-src/usr.sbin/envstat/envstat.c (revision 0517e95fd094ca2189df7c37dd3a466c21b6c0e8)
1 /* $NetBSD: envstat.c,v 1.104 2023/06/19 03:03:11 rin 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.104 2023/06/19 03:03:11 rin 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 fahrenheit */
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 
main(int argc,char ** argv)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 Fahrenheit */
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 *sstring, *p, *last, *s;
240 				char *dvstring = NULL; /* XXXGCC */
241 				unsigned count = 0;
242 
243 				s = strdup(sensors);
244 				if (s == NULL)
245 					err(EXIT_FAILURE, "strdup");
246 
247 				for ((p = strtok_r(s, ",", &last)); p;
248 				     (p = strtok_r(NULL, ",", &last))) {
249 					/* get device name */
250 					dvstring = strtok(p, ":");
251 					if (dvstring == NULL)
252 						errx(EXIT_FAILURE, "missing device name");
253 
254 					/* get sensor description */
255 					sstring = strtok(NULL, ":");
256 					if (sstring == NULL)
257 						errx(EXIT_FAILURE, "missing sensor description");
258 
259 					if (add_sensors(ndict, dict, dvstring, sstring))
260 						err(EXIT_FAILURE, "add_sensors");
261 
262 					++count;
263 				}
264 				free(s);
265 
266 				/* in case we asked for a single sensor
267 				 * show only the sensor dictionary
268 				 */
269 				if (count == 1) {
270 					prop_object_t obj, obj2;
271 
272 					obj = prop_dictionary_get(ndict, dvstring);
273 					obj2 = prop_array_get(obj, 0);
274 					prop_object_release(ndict);
275 					ndict = obj2;
276 				}
277 			}
278 
279 			prop_object_release(dict);
280 			dict = ndict;
281 		}
282 
283 		if (argc > 0) {
284 			for (; argc > 0; ++argv, --argc)
285 				config_dict_extract(dict, *argv, true);
286 		} else
287 			config_dict_dump(dict);
288 
289 	/* Remove all properties set in dictionary */
290 	} else if (flags & ENVSYS_SFLAG) {
291 		/* Close the ro descriptor */
292 		(void)prog_close(sysmonfd);
293 
294 		/* open the fd in rw mode */
295 		if ((sysmonfd = prog_open(_PATH_SYSMON, O_RDWR)) == -1)
296 			err(EXIT_FAILURE, "%s", _PATH_SYSMON);
297 
298 		dict = prop_dictionary_create();
299 		if (!dict)
300 			err(EXIT_FAILURE, "prop_dictionary_create");
301 
302 		rval = prop_dictionary_set_bool(dict,
303 						"envsys-remove-props",
304 					        true);
305 		if (!rval)
306 			err(EXIT_FAILURE, "prop_dict_set_bool");
307 
308 		/* send the dictionary to the kernel now */
309 		rval = prop_dictionary_send_ioctl(dict, sysmonfd,
310 		    ENVSYS_REMOVEPROPS);
311 		if (rval)
312 			warnx("%s", strerror(rval));
313 
314 	/* Set properties in dictionary */
315 	} else if (configfile) {
316 		/*
317 		 * Parse the configuration file.
318 		 */
319 		if ((cf = fopen(configfile, "r")) == NULL) {
320 			syslog(LOG_ERR, "fopen failed: %s", strerror(errno));
321 			errx(EXIT_FAILURE, "%s", strerror(errno));
322 		}
323 
324 		rval = send_dictionary(cf);
325 		(void)fclose(cf);
326 
327 	/* Show sensors with interval */
328 	} else if (interval) {
329 		for (;;) {
330 			rval = parse_dictionary(sysmonfd);
331 			if (rval)
332 				break;
333 
334 			(void)fflush(stdout);
335 			(void)sleep(interval);
336 		}
337 	/* Show sensors without interval */
338 	} else {
339 		rval = parse_dictionary(sysmonfd);
340 	}
341 
342 	(void)prog_close(sysmonfd);
343 
344 	return rval ? EXIT_FAILURE : EXIT_SUCCESS;
345 }
346 
347 static int
send_dictionary(FILE * cf)348 send_dictionary(FILE *cf)
349 {
350 	prop_dictionary_t kdict, udict;
351 	int error = 0;
352 
353 	/* Retrieve dictionary from kernel */
354 	error = prop_dictionary_recv_ioctl(sysmonfd,
355 	    ENVSYS_GETDICTIONARY, &kdict);
356       	if (error)
357 		return error;
358 
359 	config_parse(cf, kdict);
360 
361 	/*
362 	 * Dictionary built by the parser from the configuration file.
363 	 */
364 	udict = config_dict_parsed();
365 
366 	/*
367 	 * Close the read only descriptor and open a new one read write.
368 	 */
369 	(void)prog_close(sysmonfd);
370 	if ((sysmonfd = prog_open(_PATH_SYSMON, O_RDWR)) == -1) {
371 		error = errno;
372 		warn("%s", _PATH_SYSMON);
373 		return error;
374 	}
375 
376 	/*
377 	 * Send our sensor properties dictionary to the kernel then.
378 	 */
379 	error = prop_dictionary_send_ioctl(udict,
380 	    sysmonfd, ENVSYS_SETDICTIONARY);
381 	if (error)
382 		warnx("%s", strerror(error));
383 
384 	prop_object_release(udict);
385 	return error;
386 }
387 
388 static sensor_stats_t
find_stats_sensor(const char * desc)389 find_stats_sensor(const char *desc)
390 {
391 	sensor_stats_t stats;
392 
393 	/*
394 	 * If we matched a sensor by its description return it, otherwise
395 	 * allocate a new one.
396 	 */
397 	SIMPLEQ_FOREACH(stats, &sensor_stats_list, entries)
398 		if (strcmp(stats->desc, desc) == 0)
399 			return stats;
400 
401 	stats = calloc(1, sizeof(*stats));
402 	if (stats == NULL)
403 		return NULL;
404 
405 	(void)strlcpy(stats->desc, desc, sizeof(stats->desc));
406 	stats->min = INT32_MAX;
407 	stats->max = INT32_MIN;
408 	SIMPLEQ_INSERT_TAIL(&sensor_stats_list, stats, entries);
409 
410 	return stats;
411 }
412 
413 static int
add_sensors(prop_dictionary_t ndict,prop_dictionary_t dict,const char * dev,const char * sensor)414 add_sensors(prop_dictionary_t ndict, prop_dictionary_t dict, const char *dev, const char *sensor)
415 {
416 	prop_object_iterator_t iter, iter2;
417 	prop_object_t obj, obj2, desc;
418 	prop_array_t array, narray;
419 	prop_dictionary_t sdict;
420 	const char *dnp;
421 	unsigned int capacity = 1;
422 	uint64_t dummy;
423 	bool found = false;
424 
425 	if (prop_dictionary_count(dict) == 0)
426 		return 0;
427 
428 	narray = prop_dictionary_get(ndict, dev);
429 	if (narray)
430 		found = true;
431 	else {
432 		narray = prop_array_create_with_capacity(capacity);
433 		if (!narray)
434 			return -1;
435 		if (!prop_dictionary_set(ndict, dev, narray)) {
436 			prop_object_release(narray);
437 			return -1;
438 		}
439 	}
440 
441 	iter = prop_dictionary_iterator(dict);
442 	if (iter == NULL)
443 		goto fail;
444 	while ((obj = prop_object_iterator_next(iter)) != NULL) {
445 		array = prop_dictionary_get_keysym(dict, obj);
446 		if (prop_object_type(array) != PROP_TYPE_ARRAY)
447 			break;
448 
449 		dnp = prop_dictionary_keysym_value(obj);
450 		if (strcmp(dev, dnp))
451 			continue;
452 		found = true;
453 
454 		iter2 = prop_array_iterator(array);
455 		while ((obj = prop_object_iterator_next(iter2)) != NULL) {
456 			obj2 = prop_dictionary_get(obj, "device-properties");
457 			if (obj2) {
458 				if (!prop_dictionary_get_uint64(obj2,
459 				    "refresh-timeout", &dummy))
460 					continue;
461 			}
462 
463 			if (sensor) {
464 				desc = prop_dictionary_get(obj, "description");
465 				if (desc == NULL)
466 					continue;
467 
468 				if (!prop_string_equals_string(desc, sensor))
469 					continue;
470 			}
471 
472 			if (!prop_array_ensure_capacity(narray, capacity))
473 				goto fail;
474 
475 			sdict = prop_dictionary_copy(obj);
476 			if (sdict == NULL)
477 				goto fail;
478 			prop_array_add(narray, sdict);
479 			++capacity;
480 		}
481 		prop_object_iterator_release(iter2);
482 	}
483 	prop_object_iterator_release(iter);
484 
485 	/* drop key and array when device wasn't found */
486 	if (!found) {
487 		prop_dictionary_remove(ndict, dev);
488 		prop_object_release(narray);
489 	}
490 
491 	return 0;
492 
493 fail:
494 	prop_dictionary_remove(ndict, dev);
495 	prop_object_release(narray);
496 	return -1;
497 }
498 
499 static int
parse_dictionary(int fd)500 parse_dictionary(int fd)
501 {
502 	sensor_t sensor = NULL;
503 	dvprops_t edp = NULL;
504 	prop_array_t array;
505 	prop_dictionary_t dict;
506 	prop_object_iterator_t iter;
507 	prop_object_t obj;
508 	const char *dnp = NULL;
509 	int rval = 0;
510 
511 	/* receive dictionary from kernel */
512 	rval = prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict);
513 	if (rval)
514 		return rval;
515 
516 	/* No drivers registered? */
517 	if (prop_dictionary_count(dict) == 0) {
518 		warnx("no drivers registered");
519 		goto out;
520 	}
521 
522 	if (mydevname) {
523 		/* -d flag specified, print sensors only for this device */
524 		obj = prop_dictionary_get(dict, mydevname);
525 		if (prop_object_type(obj) != PROP_TYPE_ARRAY) {
526 			warnx("unknown device `%s'", mydevname);
527 			rval = EINVAL;
528 			goto out;
529 		}
530 
531 		rval = find_sensors(obj, mydevname, NULL);
532 		if (rval)
533 			goto out;
534 
535 	} else {
536 		/* print sensors for all devices registered */
537 		iter = prop_dictionary_iterator(dict);
538 		if (iter == NULL) {
539 			rval = EINVAL;
540 			goto out;
541 		}
542 
543 		/* iterate over the dictionary returned by the kernel */
544 		while ((obj = prop_object_iterator_next(iter)) != NULL) {
545 			array = prop_dictionary_get_keysym(dict, obj);
546 			if (prop_object_type(array) != PROP_TYPE_ARRAY) {
547 				warnx("no sensors found");
548 				rval = EINVAL;
549 				goto out;
550 			}
551 
552 			edp = calloc(1, sizeof(*edp));
553 			if (!edp) {
554 				rval = ENOMEM;
555 				goto out;
556 			}
557 
558 			dnp = prop_dictionary_keysym_value(obj);
559 			rval = find_sensors(array, dnp, edp);
560 			if (rval)
561 				goto out;
562 
563 			if (((flags & ENVSYS_LFLAG) == 0) &&
564 			    (flags & ENVSYS_DFLAG)) {
565 				(void)printf("%s (checking events every ",
566 				    dnp);
567 				if (edp->refresh_timo == 1)
568 					(void)printf("second)\n");
569 				else
570 					(void)printf("%d seconds)\n",
571 					    (int)edp->refresh_timo);
572 			}
573 
574 			free(edp);
575 			edp = NULL;
576 		}
577 		prop_object_iterator_release(iter);
578 	}
579 
580 	/* print sensors now */
581 	if (sensors)
582 		rval = check_sensors(sensors);
583 	if ((flags & ENVSYS_LFLAG) == 0 && (flags & ENVSYS_DFLAG) == 0)
584 		print_sensors();
585 	if (interval && ((flags & ENVSYS_NFLAG) == 0 || sensors == NULL))
586 		(void)printf("\n");
587 
588 out:
589 	while ((sensor = SIMPLEQ_FIRST(&sensors_list))) {
590 		SIMPLEQ_REMOVE_HEAD(&sensors_list, entries);
591 		free(sensor);
592 	}
593 	if (edp)
594 		free(edp);
595 	prop_object_release(dict);
596 	return rval;
597 }
598 
599 static int
find_sensors(prop_array_t array,const char * dvname,dvprops_t edp)600 find_sensors(prop_array_t array, const char *dvname, dvprops_t edp)
601 {
602 	prop_object_iterator_t iter;
603 	prop_object_t obj, obj1, obj2;
604 	prop_string_t state, desc = NULL;
605 	sensor_t sensor = NULL;
606 	sensor_stats_t stats = NULL;
607 
608 	iter = prop_array_iterator(array);
609 	if (!iter)
610 		return ENOMEM;
611 
612 	/* iterate over the array of dictionaries */
613 	while ((obj = prop_object_iterator_next(iter)) != NULL) {
614 		/* get the refresh-timeout property */
615 		obj2 = prop_dictionary_get(obj, "device-properties");
616 		if (obj2) {
617 			if (!edp)
618 				continue;
619 			if (!prop_dictionary_get_uint64(obj2,
620 							"refresh-timeout",
621 							&edp->refresh_timo))
622 				continue;
623 		}
624 
625 		/* new sensor coming */
626 		sensor = calloc(1, sizeof(*sensor));
627 		if (sensor == NULL) {
628 			prop_object_iterator_release(iter);
629 			return ENOMEM;
630 		}
631 
632 		/* copy device name */
633 		(void)strlcpy(sensor->dvname, dvname, sizeof(sensor->dvname));
634 
635 		/* description string */
636 		desc = prop_dictionary_get(obj, "description");
637 		if (desc) {
638 			/* copy description */
639 			(void)strlcpy(sensor->desc,
640 			    prop_string_value(desc),
641 		    	    sizeof(sensor->desc));
642 		} else {
643 			free(sensor);
644 			continue;
645 		}
646 
647 		/* type string */
648 		obj1  = prop_dictionary_get(obj, "type");
649 		if (obj1) {
650 			/* copy type */
651 			(void)strlcpy(sensor->type,
652 		    	    prop_string_value(obj1),
653 		    	    sizeof(sensor->type));
654 		} else {
655 			free(sensor);
656 			continue;
657 		}
658 
659 		/* check sensor's state */
660 		state = prop_dictionary_get(obj, "state");
661 
662 		/* mark sensors with invalid/unknown state */
663 		if ((prop_string_equals_string(state, "invalid") ||
664 		     prop_string_equals_string(state, "unknown")))
665 			sensor->invalid = true;
666 
667 		/* get current drive state string */
668 		obj1 = prop_dictionary_get(obj, "drive-state");
669 		if (obj1) {
670 			(void)strlcpy(sensor->drvstate,
671 			    prop_string_value(obj1),
672 			    sizeof(sensor->drvstate));
673 		}
674 
675 		/* get current battery capacity string */
676 		obj1 = prop_dictionary_get(obj, "battery-capacity");
677 		if (obj1) {
678 			(void)strlcpy(sensor->battcap,
679 			    prop_string_value(obj1),
680 			    sizeof(sensor->battcap));
681 		}
682 
683 		/* get current value */
684 		obj1 = prop_dictionary_get(obj, "cur-value");
685 		if (obj1)
686 			sensor->cur_value = prop_number_signed_value(obj1);
687 
688 		/* get max value */
689 		obj1 = prop_dictionary_get(obj, "max-value");
690 		if (obj1)
691 			sensor->max_value = prop_number_signed_value(obj1);
692 
693 		/* get min value */
694 		obj1 = prop_dictionary_get(obj, "min-value");
695 		if (obj1)
696 			sensor->min_value = prop_number_signed_value(obj1);
697 
698 		/* get percentage flag */
699 		obj1 = prop_dictionary_get(obj, "want-percentage");
700 		if (obj1)
701 			sensor->percentage = prop_bool_true(obj1);
702 
703 		/* get critical max value if available */
704 		obj1 = prop_dictionary_get(obj, "critical-max");
705 		if (obj1)
706 			sensor->critmax_value = prop_number_signed_value(obj1);
707 
708 		/* get maximum capacity value if available */
709 		obj1 = prop_dictionary_get(obj, "maximum-capacity");
710 		if (obj1)
711 			sensor->critmax_value = prop_number_signed_value(obj1);
712 
713 		/* get critical min value if available */
714 		obj1 = prop_dictionary_get(obj, "critical-min");
715 		if (obj1)
716 			sensor->critmin_value = prop_number_signed_value(obj1);
717 
718 		/* get critical capacity value if available */
719 		obj1 = prop_dictionary_get(obj, "critical-capacity");
720 		if (obj1)
721 			sensor->critmin_value = prop_number_signed_value(obj1);
722 
723 		/* get warning max value if available */
724 		obj1 = prop_dictionary_get(obj, "warning-max");
725 		if (obj1)
726 			sensor->warnmax_value = prop_number_signed_value(obj1);
727 
728 		/* get high capacity value if available */
729 		obj1 = prop_dictionary_get(obj, "high-capacity");
730 		if (obj1)
731 			sensor->warnmax_value = prop_number_signed_value(obj1);
732 
733 		/* get warning min value if available */
734 		obj1 = prop_dictionary_get(obj, "warning-min");
735 		if (obj1)
736 			sensor->warnmin_value = prop_number_signed_value(obj1);
737 
738 		/* get warning capacity value if available */
739 		obj1 = prop_dictionary_get(obj, "warning-capacity");
740 		if (obj1)
741 			sensor->warnmin_value = prop_number_signed_value(obj1);
742 
743 		/* print sensor names if -l was given */
744 		if (flags & ENVSYS_LFLAG) {
745 			if (width)
746 				(void)printf("%*s\n", width,
747 				    prop_string_value(desc));
748 			else
749 				(void)printf("%s\n",
750 				    prop_string_value(desc));
751 		}
752 
753 		/* Add the sensor into the list */
754 		SIMPLEQ_INSERT_TAIL(&sensors_list, sensor, entries);
755 
756 		/* Collect statistics if flag enabled */
757 		if (statistics) {
758 			/* ignore sensors not relevant for statistics */
759 			if ((strcmp(sensor->type, "Indicator") == 0) ||
760 			    (strcmp(sensor->type, "Battery charge") == 0) ||
761 			    (strcmp(sensor->type, "Drive") == 0))
762 				continue;
763 
764 			/* ignore invalid data */
765 			if (sensor->invalid)
766 				continue;
767 
768 			/* find or allocate a new statistics sensor */
769 			stats = find_stats_sensor(sensor->desc);
770 			if (stats == NULL) {
771 				free(sensor);
772 				prop_object_iterator_release(iter);
773 				return ENOMEM;
774 			}
775 
776 			/* update data */
777 			if (sensor->cur_value > stats->max)
778 				stats->max = sensor->cur_value;
779 
780 			if (sensor->cur_value < stats->min)
781 				stats->min = sensor->cur_value;
782 
783 			/* compute avg value */
784 			stats->avg =
785 			    (sensor->cur_value + stats->max + stats->min) / 3;
786 		}
787 	}
788 
789 	/* free memory */
790 	prop_object_iterator_release(iter);
791 	return 0;
792 }
793 
794 static int
check_sensors(const char * str)795 check_sensors(const char *str)
796 {
797 	sensor_t sensor = NULL;
798 	char *dvstring, *sstring, *p, *last, *s;
799 	bool sensor_found = false;
800 
801 	if ((s = strdup(str)) == NULL)
802 		return errno;
803 
804 	/*
805 	 * Parse device name and sensor description and find out
806 	 * if the sensor is valid.
807 	 */
808 	for ((p = strtok_r(s, ",", &last)); p;
809 	     (p = strtok_r(NULL, ",", &last))) {
810 		/* get device name */
811 		dvstring = strtok(p, ":");
812 		if (dvstring == NULL) {
813 			warnx("missing device name");
814 			goto out;
815 		}
816 
817 		/* get sensor description */
818 		sstring = strtok(NULL, ":");
819 		if (sstring == NULL) {
820 			warnx("missing sensor description");
821 			goto out;
822 		}
823 
824 		SIMPLEQ_FOREACH(sensor, &sensors_list, entries) {
825 			/* skip until we match device */
826 			if (strcmp(dvstring, sensor->dvname))
827 				continue;
828 			if (strcmp(sstring, sensor->desc) == 0) {
829 				sensor->visible = true;
830 				sensor_found = true;
831 				break;
832 			}
833 		}
834 		if (sensor_found == false) {
835 			warnx("unknown sensor `%s' for device `%s'",
836 		       	    sstring, dvstring);
837 			goto out;
838 		}
839 		sensor_found = false;
840 	}
841 
842 	/* check if all sensors were ok, and error out if not */
843 	SIMPLEQ_FOREACH(sensor, &sensors_list, entries)
844 		if (sensor->visible) {
845 			free(s);
846 			return 0;
847 		}
848 
849 	warnx("no sensors selected to display");
850 out:
851 	free(s);
852 	return EINVAL;
853 }
854 
855 static void
print_sensors(void)856 print_sensors(void)
857 {
858 	sensor_t sensor;
859 	sensor_stats_t stats = NULL;
860 	size_t maxlen = 0, ilen;
861 	double temp = 0;
862 	const char *invalid = "N/A", *degrees, *tmpstr, *stype;
863 	const char *a, *b, *c, *d, *e, *units;
864 	const char *sep;
865 	int flen;
866 	bool nflag = (flags & ENVSYS_NFLAG) != 0;
867 
868 	tmpstr = stype = d = e = NULL;
869 
870 	/* find the longest description */
871 	SIMPLEQ_FOREACH(sensor, &sensors_list, entries)
872 		if (strlen(sensor->desc) > maxlen)
873 			maxlen = strlen(sensor->desc);
874 
875 	if (width)
876 		maxlen = width;
877 
878 	/*
879 	 * Print a header at the bottom only once showing different
880 	 * members if the statistics flag is set or not.
881 	 *
882 	 * As bonus if -s is set, only print this header every 10 iterations
883 	 * to avoid redundancy... like vmstat(1).
884 	 */
885 
886 	a = "Current";
887 	units = "Unit";
888 	if (statistics) {
889 		b = "Max";
890 		c = "Min";
891 		d = "Avg";
892 	} else {
893 		b = "CritMax";
894 		c = "WarnMax";
895 		d = "WarnMin";
896 		e = "CritMin";
897 	}
898 
899 	if (!nflag) {
900 		if (!sensors || (!header_passes && sensors) ||
901 		    (header_passes == 10 && sensors)) {
902 			if (statistics)
903 				(void)printf("%s%*s  %9s %8s %8s %8s %6s\n",
904 				    mydevname ? "" : "  ", (int)maxlen,
905 				    "", a, b, c, d, units);
906 			else
907 				(void)printf("%s%*s  %9s %8s %8s %8s %8s %5s\n",
908 				    mydevname ? "" : "  ", (int)maxlen,
909 				    "", a, b, c, d, e, units);
910 			if (sensors && header_passes == 10)
911 				header_passes = 0;
912 		}
913 		if (sensors)
914 			header_passes++;
915 
916 		sep = ":";
917 		flen = 10;
918 	} else {
919 		sep = "";
920 		flen = 1;
921 	}
922 
923 	/* print the sensors */
924 	SIMPLEQ_FOREACH(sensor, &sensors_list, entries) {
925 		/* skip sensors that were not marked as visible */
926 		if (sensors && !sensor->visible)
927 			continue;
928 
929 		/* skip invalid sensors if -I is set */
930 		if ((flags & ENVSYS_IFLAG) && sensor->invalid)
931 			continue;
932 
933 		/* print device name */
934 		if (!nflag && !mydevname) {
935 			if (tmpstr == NULL || strcmp(tmpstr, sensor->dvname))
936 				printf("[%s]\n", sensor->dvname);
937 
938 			tmpstr = sensor->dvname;
939 		}
940 
941 		/* find out the statistics sensor */
942 		if (statistics) {
943 			stats = find_stats_sensor(sensor->desc);
944 			if (stats == NULL) {
945 				/* No statistics for this sensor */
946 				continue;
947 			}
948 		}
949 
950 		if (!nflag) {
951 			/* print sensor description */
952 			(void)printf("%s%*.*s", mydevname ? "" : "  ",
953 			    (int)maxlen,
954 			    (int)maxlen, sensor->desc);
955 		}
956 
957 		/* print invalid string */
958 		if (sensor->invalid) {
959 			(void)printf("%s%*s\n", sep, flen, invalid);
960 			continue;
961 		}
962 
963 		/*
964 		 * Indicator and Battery charge sensors.
965 		 */
966 		if ((strcmp(sensor->type, "Indicator") == 0) ||
967 		    (strcmp(sensor->type, "Battery charge") == 0)) {
968 
969 			(void)printf("%s%*s", sep, flen,
970 			     sensor->cur_value ? "TRUE" : "FALSE");
971 
972 /* convert and print a temp value in degC, degF, or Kelvin */
973 #define PRINTTEMP(a)						\
974 do {								\
975 	if (a) {						\
976 		temp = ((a) / 1000000.0);			\
977 		if (flags & ENVSYS_FFLAG) {			\
978 			temp = temp * (9.0 / 5.0) - 459.67;	\
979 			degrees = "degF";			\
980 		} else if (flags & ENVSYS_KFLAG) {		\
981 			degrees = "K";				\
982 		} else {					\
983 			temp = temp - 273.15;			\
984 			degrees = "degC";			\
985 		}						\
986 		(void)printf("%*.3f", (int)ilen, temp);	\
987 		ilen = 9;					\
988 	} else							\
989 		ilen += 9;					\
990 } while (0)
991 
992 		/* temperatures */
993 		} else if (strcmp(sensor->type, "Temperature") == 0) {
994 
995 			ilen = nflag ? 1 : 10;
996 			degrees = "";
997 			(void)printf("%s",sep);
998 			PRINTTEMP(sensor->cur_value);
999 			stype = degrees;
1000 
1001 			if (statistics) {
1002 				/* show statistics if flag set */
1003 				PRINTTEMP(stats->max);
1004 				PRINTTEMP(stats->min);
1005 				PRINTTEMP(stats->avg);
1006 				ilen += 2;
1007 			} else if (!nflag) {
1008 				PRINTTEMP(sensor->critmax_value);
1009 				PRINTTEMP(sensor->warnmax_value);
1010 				PRINTTEMP(sensor->warnmin_value);
1011 				PRINTTEMP(sensor->critmin_value);
1012 			}
1013 			if (!nflag)
1014 				(void)printf("%*s", (int)ilen - 3, stype);
1015 #undef PRINTTEMP
1016 
1017 		/* fans */
1018 		} else if (strcmp(sensor->type, "Fan") == 0) {
1019 			stype = "RPM";
1020 
1021 			(void)printf("%s%*u", sep, flen, sensor->cur_value);
1022 
1023 			ilen = 8;
1024 			if (statistics) {
1025 				/* show statistics if flag set */
1026 				(void)printf(" %8u %8u %8u",
1027 				    stats->max, stats->min, stats->avg);
1028 				ilen += 2;
1029 			} else if (!nflag) {
1030 				if (sensor->critmax_value) {
1031 					(void)printf(" %*u", (int)ilen,
1032 					    sensor->critmax_value);
1033 					ilen = 8;
1034 				} else
1035 					ilen += 9;
1036 
1037 				if (sensor->warnmax_value) {
1038 					(void)printf(" %*u", (int)ilen,
1039 					    sensor->warnmax_value);
1040 					ilen = 8;
1041 				} else
1042 					ilen += 9;
1043 
1044 				if (sensor->warnmin_value) {
1045 					(void)printf(" %*u", (int)ilen,
1046 					    sensor->warnmin_value);
1047 					ilen = 8;
1048 				} else
1049 					ilen += 9;
1050 
1051 				if (sensor->critmin_value) {
1052 					(void)printf( " %*u", (int)ilen,
1053 					    sensor->critmin_value);
1054 					ilen = 8;
1055 				} else
1056 					ilen += 9;
1057 
1058 			}
1059 
1060 			if (!nflag)
1061 				(void)printf(" %*s", (int)ilen - 3, stype);
1062 
1063 		/* integers */
1064 		} else if (strcmp(sensor->type, "Integer") == 0) {
1065 
1066 			stype = "none";
1067 
1068 			(void)printf("%s%*d", sep, flen, sensor->cur_value);
1069 
1070 			ilen = 8;
1071 
1072 /* Print percentage of max_value */
1073 #define PRINTPCT(a)							\
1074 do {									\
1075 	if (sensor->max_value) {					\
1076 		(void)printf(" %*.3f%%", (int)ilen,			\
1077 			((a) * 100.0) / sensor->max_value);		\
1078 		ilen = 8;						\
1079 	} else								\
1080 		ilen += 9;						\
1081 } while ( /* CONSTCOND*/ 0 )
1082 
1083 /* Print an integer sensor value */
1084 #define PRINTINT(a)							\
1085 do {									\
1086 	(void)printf(" %*u", (int)ilen, (a));				\
1087 	ilen = 8;							\
1088 } while ( /* CONSTCOND*/ 0 )
1089 
1090 			if (statistics) {
1091 				if (sensor->percentage) {
1092 					PRINTPCT(stats->max);
1093 					PRINTPCT(stats->min);
1094 					PRINTPCT(stats->avg);
1095 				} else {
1096 					PRINTINT(stats->max);
1097 					PRINTINT(stats->min);
1098 					PRINTINT(stats->avg);
1099 				}
1100 				ilen += 2;
1101 			} else if (!nflag) {
1102 				if (sensor->percentage) {
1103 					PRINTPCT(sensor->critmax_value);
1104 					PRINTPCT(sensor->warnmax_value);
1105 					PRINTPCT(sensor->warnmin_value);
1106 					PRINTPCT(sensor->critmin_value);
1107 				} else {
1108 					PRINTINT(sensor->critmax_value);
1109 					PRINTINT(sensor->warnmax_value);
1110 					PRINTINT(sensor->warnmin_value);
1111 					PRINTINT(sensor->critmin_value);
1112 				}
1113 			}
1114 
1115 			if (!nflag)
1116 				(void)printf("%*s", (int)ilen - 3, stype);
1117 
1118 #undef PRINTINT
1119 #undef PRINTPCT
1120 
1121 		/* drives  */
1122 		} else if (strcmp(sensor->type, "Drive") == 0) {
1123 
1124 			(void)printf("%s%*s", sep, flen, sensor->drvstate);
1125 
1126 		/* Battery capacity */
1127 		} else if (strcmp(sensor->type, "Battery capacity") == 0) {
1128 
1129 			(void)printf("%s%*s", sep, flen, sensor->battcap);
1130 
1131 		/* Illuminance */
1132 		} else if (strcmp(sensor->type, "Illuminance") == 0) {
1133 
1134 			stype = "lux";
1135 
1136 			(void)printf("%s%*u", sep, flen, sensor->cur_value);
1137 
1138 			ilen = 8;
1139 			if (statistics) {
1140 				/* show statistics if flag set */
1141 				(void)printf(" %8u %8u %8u",
1142 				    stats->max, stats->min, stats->avg);
1143 				ilen += 2;
1144 			} else if (!nflag) {
1145 				if (sensor->critmax_value) {
1146 					(void)printf(" %*u", (int)ilen,
1147 					    sensor->critmax_value);
1148 					ilen = 8;
1149 				} else
1150 					ilen += 9;
1151 
1152 				if (sensor->warnmax_value) {
1153 					(void)printf(" %*u", (int)ilen,
1154 					    sensor->warnmax_value);
1155 					ilen = 8;
1156 				} else
1157 					ilen += 9;
1158 
1159 				if (sensor->warnmin_value) {
1160 					(void)printf(" %*u", (int)ilen,
1161 					    sensor->warnmin_value);
1162 					ilen = 8;
1163 				} else
1164 					ilen += 9;
1165 
1166 				if (sensor->critmin_value) {
1167 					(void)printf( " %*u", (int)ilen,
1168 					    sensor->critmin_value);
1169 					ilen = 8;
1170 				} else
1171 					ilen += 9;
1172 
1173 			}
1174 
1175 			if (!nflag)
1176 				(void)printf(" %*s", (int)ilen - 3, stype);
1177 
1178 		/* Pressure */
1179 		} else if (strcmp(sensor->type, "pressure") == 0) {
1180 			stype = "hPa";
1181 
1182 			(void)printf("%s%*.3f", sep, flen,
1183 			    sensor->cur_value / 10000.0);
1184 
1185 			ilen = 8;
1186 			if (statistics) {
1187 				/* show statistics if flag set */
1188 				(void)printf("  %.3f  %.3f  %.3f",
1189 				    stats->max / 10000.0, stats->min / 10000.0, stats->avg / 10000.0);
1190 				ilen += 2;
1191 			} else if (!nflag) {
1192 				if (sensor->critmax_value) {
1193 					(void)printf(" %*u", (int)ilen,
1194 					    sensor->critmax_value);
1195 					ilen = 8;
1196 				} else
1197 					ilen += 9;
1198 
1199 				if (sensor->warnmax_value) {
1200 					(void)printf(" %*u", (int)ilen,
1201 					    sensor->warnmax_value);
1202 					ilen = 8;
1203 				} else
1204 					ilen += 9;
1205 
1206 				if (sensor->warnmin_value) {
1207 					(void)printf(" %*u", (int)ilen,
1208 					    sensor->warnmin_value);
1209 					ilen = 8;
1210 				} else
1211 					ilen += 9;
1212 
1213 				if (sensor->critmin_value) {
1214 					(void)printf( " %*u", (int)ilen,
1215 					    sensor->critmin_value);
1216 					ilen = 8;
1217 				} else
1218 					ilen += 9;
1219 
1220 			}
1221 
1222 			if (!nflag)
1223 				(void)printf(" %*s", (int)ilen - 3, stype);
1224 
1225 		/* everything else */
1226 		} else {
1227 			if (strcmp(sensor->type, "Voltage DC") == 0)
1228 				stype = "V";
1229 			else if (strcmp(sensor->type, "Voltage AC") == 0)
1230 				stype = "VAC";
1231 			else if (strcmp(sensor->type, "Ampere") == 0)
1232 				stype = "A";
1233 			else if (strcmp(sensor->type, "Watts") == 0)
1234 				stype = "W";
1235 			else if (strcmp(sensor->type, "Ohms") == 0)
1236 				stype = "Ohms";
1237 			else if (strcmp(sensor->type, "Watt hour") == 0)
1238 				stype = "Wh";
1239 			else if (strcmp(sensor->type, "Ampere hour") == 0)
1240 				stype = "Ah";
1241 			else if (strcmp(sensor->type, "relative Humidity") == 0)
1242 				stype = "%rH";
1243 			else
1244 				stype = "?";
1245 
1246 			(void)printf("%s%*.3f", sep, flen,
1247 			    sensor->cur_value / 1000000.0);
1248 
1249 			ilen = 9;
1250 
1251 /* Print percentage of max_value */
1252 #define PRINTPCT(a)							\
1253 do {									\
1254 	if ((a) && sensor->max_value) {					\
1255 		(void)printf("%*.3f%%", (int)ilen,			\
1256 			((a) * 100.0) / sensor->max_value);		\
1257 		ilen = 8;						\
1258 	} else								\
1259 		ilen += 9;						\
1260 } while ( /* CONSTCOND*/ 0 )
1261 
1262 /* Print a generic sensor value */
1263 #define PRINTVAL(a)							\
1264 do {									\
1265 	if ((a)) {							\
1266 		(void)printf("%*.3f", (int)ilen, (a) / 1000000.0);	\
1267 		ilen = 9;						\
1268 	} else								\
1269 		ilen += 9;						\
1270 } while ( /* CONSTCOND*/ 0 )
1271 
1272 			if (statistics) {
1273 				if (sensor->percentage) {
1274 					PRINTPCT(stats->max);
1275 					PRINTPCT(stats->min);
1276 					PRINTPCT(stats->avg);
1277 				} else {
1278 					PRINTVAL(stats->max);
1279 					PRINTVAL(stats->min);
1280 					PRINTVAL(stats->avg);
1281 				}
1282 				ilen += 2;
1283 			} else if (!nflag) {
1284 				if (sensor->percentage) {
1285 					PRINTPCT(sensor->critmax_value);
1286 					PRINTPCT(sensor->warnmax_value);
1287 					PRINTPCT(sensor->warnmin_value);
1288 					PRINTPCT(sensor->critmin_value);
1289 				} else {
1290 
1291 					PRINTVAL(sensor->critmax_value);
1292 					PRINTVAL(sensor->warnmax_value);
1293 					PRINTVAL(sensor->warnmin_value);
1294 					PRINTVAL(sensor->critmin_value);
1295 				}
1296 			}
1297 #undef PRINTPCT
1298 #undef PRINTVAL
1299 
1300 			if (!nflag) {
1301 				(void)printf(" %*s", (int)ilen - 4, stype);
1302 				if (sensor->percentage && sensor->max_value) {
1303 					(void)printf(" (%5.2f%%)",
1304 					    (sensor->cur_value * 100.0) /
1305 					    sensor->max_value);
1306 				}
1307 			}
1308 		}
1309 		(void)printf("\n");
1310 	}
1311 }
1312 
1313 static int
usage(void)1314 usage(void)
1315 {
1316 	(void)fprintf(stderr, "Usage: %s [-DfIklnrST] ", getprogname());
1317 	(void)fprintf(stderr, "[-c file] [-d device] [-i interval] ");
1318 	(void)fprintf(stderr, "[-s device:sensor,...] [-w width]\n");
1319 	(void)fprintf(stderr, "       %s ", getprogname());
1320 	(void)fprintf(stderr, "[-d device] ");
1321 	(void)fprintf(stderr, "[-s device:sensor,...] ");
1322 	(void)fprintf(stderr, "-x [property]\n");
1323 	exit(EXIT_FAILURE);
1324 	/* NOTREACHED */
1325 }
1326