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 */
65typedef 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 */
85typedef 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 */
94typedef 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 */
100static SIMPLEQ_HEAD(, envsys_sensor) sensors_list =
101    SIMPLEQ_HEAD_INITIALIZER(sensors_list);
102
103/* A simple queue to manage statistics for all sensors */
104static SIMPLEQ_HEAD(, envsys_sensor_stats) sensor_stats_list =
105    SIMPLEQ_HEAD_INITIALIZER(sensor_stats_list);
106
107static unsigned int 	interval, flags, width;
108static char 		*mydevname, *sensors;
109static bool 		statistics;
110static u_int		header_passes;
111
112static int 		parse_dictionary(int);
113static int		add_sensors(prop_dictionary_t, prop_dictionary_t, const char *, const char *);
114static int 		send_dictionary(FILE *);
115static int 		find_sensors(prop_array_t, const char *, dvprops_t);
116static void 		print_sensors(void);
117static int 		check_sensors(const char *);
118static int 		usage(void);
119
120static int		sysmonfd; /* fd of /dev/sysmon */
121
122int 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
347static int
348send_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
388static sensor_stats_t
389find_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
413static int
414add_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
493fail:
494	prop_dictionary_remove(ndict, dev);
495	prop_object_release(narray);
496	return -1;
497}
498
499static int
500parse_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
588out:
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
599static int
600find_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
794static int
795check_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");
850out:
851	free(s);
852	return EINVAL;
853}
854
855static void
856print_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)						\
974do {								\
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)							\
1074do {									\
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)							\
1085do {									\
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)							\
1253do {									\
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)							\
1264do {									\
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
1313static int
1314usage(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