envstat.c revision 1.56 1 /* $NetBSD: envstat.c,v 1.56 2007/10/07 04:16:48 xtraeme Exp $ */
2
3 /*-
4 * Copyright (c) 2007 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 /*
29 * TODO
30 *
31 * o Some checks should be added to ensure that the user does not
32 * set unwanted values for the critical limits.
33 */
34
35 #include <sys/cdefs.h>
36 #ifndef lint
37 __RCSID("$NetBSD: envstat.c,v 1.56 2007/10/07 04:16:48 xtraeme Exp $");
38 #endif /* not lint */
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <stdbool.h>
43 #include <string.h>
44 #include <unistd.h>
45 #include <fcntl.h>
46 #include <err.h>
47 #include <errno.h>
48 #include <syslog.h>
49 #include <prop/proplib.h>
50 #include <sys/envsys.h>
51
52 #include "envstat.h"
53
54 #define _PATH_DEV_SYSMON "/dev/sysmon"
55
56 #define ENVSYS_DFLAG 0x00000001 /* list registered devices */
57 #define ENVSYS_FFLAG 0x00000002 /* show temp in farenheit */
58 #define ENVSYS_LFLAG 0x00000004 /* list sensors */
59 #define ENVSYS_XFLAG 0x00000008 /* externalize dictionary */
60 #define ENVSYS_IFLAG 0x00000010 /* skips invalid sensors */
61 #define ENVSYS_SFLAG 0x00000020 /* removes all properties set */
62
63 struct envsys_sensor {
64 bool invalid;
65 bool visible;
66 bool percentage;
67 int32_t cur_value;
68 int32_t max_value;
69 int32_t min_value;
70 int32_t avg_value;
71 int32_t critcap_value;
72 int32_t critmin_value;
73 int32_t critmax_value;
74 char desc[ENVSYS_DESCLEN];
75 char type[ENVSYS_DESCLEN];
76 char drvstate[ENVSYS_DESCLEN];
77 char battstate[ENVSYS_DESCLEN];
78 char dvname[ENVSYS_DESCLEN];
79 };
80
81 static unsigned int interval, flags, width;
82 static char *mydevname, *sensors;
83 static struct envsys_sensor *gesen;
84 static size_t gnelems, newsize;
85
86 static int parse_dictionary(int);
87 static int send_dictionary(FILE *, int);
88 static int find_sensors(prop_array_t, const char *);
89 static void print_sensors(struct envsys_sensor *, size_t, const char *);
90 static int check_sensors(struct envsys_sensor *, char *, size_t);
91 static int usage(void);
92
93
94 int main(int argc, char **argv)
95 {
96 prop_dictionary_t dict;
97 int c, fd, rval;
98 char *endptr, *configfile = NULL;
99 FILE *cf;
100
101 rval = flags = interval = width = 0;
102 newsize = gnelems = 0;
103 gesen = NULL;
104
105 setprogname(argv[0]);
106
107 while ((c = getopt(argc, argv, "c:Dd:fIi:lrSs:w:x")) != -1) {
108 switch (c) {
109 case 'c': /* configuration file */
110 configfile = strdup(optarg);
111 if (configfile == NULL)
112 err(EXIT_FAILURE, "strdup");
113 break;
114 case 'D': /* list registered devices */
115 flags |= ENVSYS_DFLAG;
116 break;
117 case 'd': /* show sensors of a specific device */
118 mydevname = strdup(optarg);
119 if (mydevname == NULL)
120 err(EXIT_FAILURE, "strdup");
121 break;
122 case 'f': /* display temperature in Farenheit */
123 flags |= ENVSYS_FFLAG;
124 break;
125 case 'I': /* Skips invalid sensors */
126 flags |= ENVSYS_IFLAG;
127 break;
128 case 'i': /* wait time between intervals */
129 interval = (unsigned int)strtoul(optarg, &endptr, 10);
130 if (*endptr != '\0')
131 errx(EXIT_FAILURE, "bad interval '%s'", optarg);
132 break;
133 case 'l': /* list sensors */
134 flags |= ENVSYS_LFLAG;
135 break;
136 case 'r':
137 /*
138 * This flag doesn't do anything... it's only here for
139 * compatibility with the old implementation.
140 */
141 break;
142 case 'S':
143 flags |= ENVSYS_SFLAG;
144 break;
145 case 's': /* only show specified sensors */
146 sensors = strdup(optarg);
147 if (sensors == NULL)
148 err(EXIT_FAILURE, "strdup");
149 break;
150 case 'w': /* width value for the lines */
151 width = strtoul(optarg, &endptr, 10);
152 if (*endptr != '\0')
153 errx(EXIT_FAILURE, "bad width '%s'", optarg);
154 break;
155 case 'x': /* print the dictionary in raw format */
156 flags |= ENVSYS_XFLAG;
157 break;
158 case '?':
159 default:
160 usage();
161 /* NOTREACHED */
162 }
163 }
164
165 argc -= optind;
166 argv += optind;
167
168 if (argc > 0)
169 usage();
170
171 if ((fd = open(_PATH_DEV_SYSMON, O_RDONLY)) == -1)
172 err(EXIT_FAILURE, "%s", _PATH_DEV_SYSMON);
173
174 if (flags & ENVSYS_XFLAG) {
175 rval = prop_dictionary_recv_ioctl(fd,
176 ENVSYS_GETDICTIONARY,
177 &dict);
178 if (rval)
179 errx(EXIT_FAILURE, "%s", strerror(rval));
180
181 config_dict_dump(dict);
182
183 } else if (flags & ENVSYS_SFLAG) {
184 (void)close(fd);
185
186 if ((fd = open(_PATH_DEV_SYSMON, O_RDWR)) == -1)
187 err(EXIT_FAILURE, "%s", _PATH_DEV_SYSMON);
188
189 dict = prop_dictionary_create();
190 if (!dict)
191 err(EXIT_FAILURE, "prop_dictionary_create");
192
193 rval = prop_dictionary_set_bool(dict,
194 "envsys-remove-props",
195 true);
196 if (!rval)
197 err(EXIT_FAILURE, "prop_dict_set_bool");
198
199 rval = prop_dictionary_send_ioctl(dict, fd, ENVSYS_REMOVEPROPS);
200 if (rval)
201 warnx("%s", strerror(rval));
202
203 } else if (configfile) {
204 /*
205 * Parse the configuration file.
206 */
207 if ((cf = fopen(configfile, "r")) == NULL) {
208 syslog(LOG_ERR, "fopen failed: %s", strerror(errno));
209 errx(EXIT_FAILURE, "%s", strerror(errno));
210 }
211
212 rval = send_dictionary(cf, fd);
213 (void)fclose(cf);
214
215 #define MISSING_FLAG() \
216 do { \
217 if (sensors && !mydevname) \
218 errx(EXIT_FAILURE, "-s requires -d"); \
219 } while (/* CONSTCOND */ 0)
220
221 } else if (interval) {
222 MISSING_FLAG();
223 for (;;) {
224 rval = parse_dictionary(fd);
225 if (rval)
226 break;
227
228 (void)fflush(stdout);
229 (void)sleep(interval);
230 }
231 } else {
232 MISSING_FLAG();
233 rval = parse_dictionary(fd);
234 }
235
236 if (sensors)
237 free(sensors);
238 if (mydevname)
239 free(mydevname);
240 (void)close(fd);
241
242 return rval ? EXIT_FAILURE : EXIT_SUCCESS;
243 }
244
245 static int
246 send_dictionary(FILE *cf, int fd)
247 {
248 prop_dictionary_t kdict, udict;
249 int error = 0;
250
251 error = prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &kdict);
252 if (error)
253 return error;
254
255 config_parse(cf, kdict);
256
257 /*
258 * Dictionary built by the parser from the configuration file.
259 */
260 udict = config_dict_parsed();
261
262 /*
263 * Close the read only descriptor and open a new one read write.
264 */
265 (void)close(fd);
266 if ((fd = open(_PATH_DEV_SYSMON, O_RDWR)) == -1) {
267 error = errno;
268 warn("%s", _PATH_DEV_SYSMON);
269 return error;
270 }
271
272 /*
273 * Send our dictionary to the kernel then.
274 */
275 error = prop_dictionary_send_ioctl(udict, fd, ENVSYS_SETDICTIONARY);
276 if (error)
277 warnx("%s", strerror(error));
278
279 prop_object_release(udict);
280 return error;
281 }
282
283 static int
284 parse_dictionary(int fd)
285 {
286 prop_array_t array;
287 prop_dictionary_t dict;
288 prop_object_iterator_t iter;
289 prop_object_t obj;
290 const char *dnp = NULL;
291 int rval = 0;
292
293 /* receive dictionary from kernel */
294 rval = prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict);
295 if (rval)
296 return rval;
297
298 if (prop_dictionary_count(dict) == 0) {
299 warnx("no drivers registered");
300 goto out;
301 }
302
303 if (mydevname) {
304 obj = prop_dictionary_get(dict, mydevname);
305 if (prop_object_type(obj) != PROP_TYPE_ARRAY) {
306 warnx("unknown device `%s'", mydevname);
307 rval = EINVAL;
308 goto out;
309 }
310
311 rval = find_sensors(obj, mydevname);
312 if (rval)
313 goto out;
314
315 if ((flags & ENVSYS_LFLAG) == 0)
316 print_sensors(gesen, gnelems, mydevname);
317 if (interval)
318 (void)printf("\n");
319 } else {
320 iter = prop_dictionary_iterator(dict);
321 if (iter == NULL) {
322 rval = EINVAL;
323 goto out;
324 }
325
326 /* iterate over the dictionary returned by the kernel */
327 while ((obj = prop_object_iterator_next(iter)) != NULL) {
328
329 array = prop_dictionary_get_keysym(dict, obj);
330 if (prop_object_type(array) != PROP_TYPE_ARRAY) {
331 warnx("no sensors found");
332 rval = EINVAL;
333 goto out;
334 }
335
336 dnp = prop_dictionary_keysym_cstring_nocopy(obj);
337
338 if (flags & ENVSYS_DFLAG) {
339 (void)printf("%s\n", dnp);
340 continue;
341 } else {
342 (void)printf("[%s]\n", dnp);
343 rval = find_sensors(array, dnp);
344 if (rval)
345 goto out;
346 }
347
348 if ((flags & ENVSYS_LFLAG) == 0)
349 print_sensors(gesen, gnelems, dnp);
350 if (interval)
351 (void)printf("\n");
352 }
353
354 prop_object_iterator_release(iter);
355 }
356
357 out:
358 if (gesen) {
359 free(gesen);
360 gesen = NULL;
361 gnelems = 0;
362 newsize = 0;
363 }
364 prop_object_release(dict);
365 return rval;
366 }
367
368 static int
369 find_sensors(prop_array_t array, const char *dvname)
370 {
371 prop_object_iterator_t iter;
372 prop_object_t obj, obj1;
373 prop_string_t state, desc = NULL;
374 struct envsys_sensor *esen = NULL;
375 int rval = 0;
376 char *str = NULL;
377
378 newsize += prop_array_count(array) * sizeof(*gesen);
379 esen = realloc(gesen, newsize);
380 if (esen == NULL) {
381 if (gesen)
382 free(gesen);
383 gesen = NULL;
384 return ENOMEM;
385 }
386 gesen = esen;
387
388 iter = prop_array_iterator(array);
389 if (!iter)
390 return EINVAL;
391
392 /* iterate over the array of dictionaries */
393 while ((obj = prop_object_iterator_next(iter)) != NULL) {
394
395 /* copy device name */
396 (void)strlcpy(gesen[gnelems].dvname, dvname,
397 sizeof(gesen[gnelems].dvname));
398
399 gesen[gnelems].visible = false;
400
401 /* check sensor's state */
402 state = prop_dictionary_get(obj, "state");
403
404 /* mark invalid sensors */
405 if (prop_string_equals_cstring(state, "invalid"))
406 gesen[gnelems].invalid = true;
407 else
408 gesen[gnelems].invalid = false;
409
410 /* description string */
411 desc = prop_dictionary_get(obj, "description");
412 if (desc) {
413 /* copy description */
414 (void)strlcpy(gesen[gnelems].desc,
415 prop_string_cstring_nocopy(desc),
416 sizeof(gesen[gnelems].desc));
417 } else
418 continue;
419
420 /* type string */
421 obj1 = prop_dictionary_get(obj, "type");
422 /* copy type */
423 (void)strlcpy(gesen[gnelems].type,
424 prop_string_cstring_nocopy(obj1),
425 sizeof(gesen[gnelems].type));
426
427 /* get current drive state string */
428 obj1 = prop_dictionary_get(obj, "drive-state");
429 if (obj1)
430 (void)strlcpy(gesen[gnelems].drvstate,
431 prop_string_cstring_nocopy(obj1),
432 sizeof(gesen[gnelems].drvstate));
433
434 /* get current battery state string */
435 obj1 = prop_dictionary_get(obj, "battery-state");
436 if (obj1)
437 (void)strlcpy(gesen[gnelems].battstate,
438 prop_string_cstring_nocopy(obj1),
439 sizeof(gesen[gnelems].battstate));
440
441 /* get current value */
442 obj1 = prop_dictionary_get(obj, "cur-value");
443 gesen[gnelems].cur_value = prop_number_integer_value(obj1);
444
445 /* get max value */
446 obj1 = prop_dictionary_get(obj, "max-value");
447 if (obj1)
448 gesen[gnelems].max_value =
449 prop_number_integer_value(obj1);
450 else
451 gesen[gnelems].max_value = 0;
452
453 /* get min value */
454 obj1 = prop_dictionary_get(obj, "min-value");
455 if (obj1)
456 gesen[gnelems].min_value =
457 prop_number_integer_value(obj1);
458 else
459 gesen[gnelems].min_value = 0;
460
461 /* get avg value */
462 obj1 = prop_dictionary_get(obj, "avg-value");
463 if (obj1)
464 gesen[gnelems].avg_value =
465 prop_number_integer_value(obj1);
466 else
467 gesen[gnelems].avg_value = 0;
468
469 /* get percentage flag */
470 obj1 = prop_dictionary_get(obj, "want-percentage");
471 if (obj1)
472 gesen[gnelems].percentage = prop_bool_true(obj1);
473
474 /* get critical max value if available */
475 obj1 = prop_dictionary_get(obj, "critical-max");
476 if (obj1) {
477 gesen[gnelems].critmax_value =
478 prop_number_integer_value(obj1);
479 } else
480 gesen[gnelems].critmax_value = 0;
481
482 /* get critical min value if available */
483 obj1 = prop_dictionary_get(obj, "critical-min");
484 if (obj1) {
485 gesen[gnelems].critmin_value =
486 prop_number_integer_value(obj1);
487 } else
488 gesen[gnelems].critmin_value = 0;
489
490 /* get critical capacity value if available */
491 obj1 = prop_dictionary_get(obj, "critical-capacity");
492 if (obj1) {
493 gesen[gnelems].critcap_value =
494 prop_number_integer_value(obj1);
495 } else
496 gesen[gnelems].critcap_value = 0;
497
498 /* pass to the next struct and increase the counter */
499 gnelems++;
500
501 /* print sensor names if -l was given */
502 if (flags & ENVSYS_LFLAG) {
503 if (width)
504 (void)printf("%*s\n", width,
505 prop_string_cstring_nocopy(desc));
506 else
507 (void)printf("%s\n",
508 prop_string_cstring_nocopy(desc));
509 }
510 }
511
512 /* free memory */
513 prop_object_iterator_release(iter);
514
515 /*
516 * if -s was specified, we need a way to mark if a sensor
517 * was found.
518 */
519 if (sensors) {
520 str = strdup(sensors);
521 if (!str)
522 return ENOMEM;
523
524 rval = check_sensors(gesen, str, gnelems);
525 free(str);
526 }
527
528 return rval;
529 }
530
531 static int
532 check_sensors(struct envsys_sensor *es, char *str, size_t nelems)
533 {
534 int i;
535 char *sname;
536
537 sname = strtok(str, ",");
538 while (sname) {
539 for (i = 0; i < nelems; i++) {
540 if (strcmp(sname, es[i].desc) == 0) {
541 es[i].visible = true;
542 break;
543 }
544 }
545 if (i >= nelems) {
546 if (mydevname) {
547 warnx("unknown sensor `%s' for device `%s'",
548 sname, mydevname);
549 return EINVAL;
550 }
551 }
552 sname = strtok(NULL, ",");
553 }
554
555 /* check if all sensors were ok, and error out if not */
556 for (i = 0; i < nelems; i++) {
557 if (es[i].visible)
558 return 0;
559 }
560
561 warnx("no sensors selected to display");
562 return EINVAL;
563 }
564
565 static void
566 print_sensors(struct envsys_sensor *es, size_t nelems, const char *dvname)
567 {
568 size_t maxlen = 0;
569 double temp = 0;
570 const char *invalid = "N/A";
571 const char *degrees = NULL;
572 int i;
573
574 /* find the longest description */
575 for (i = 0; i < nelems; i++) {
576 if (strlen(es[i].desc) > maxlen)
577 maxlen = strlen(es[i].desc);
578 }
579
580 if (width)
581 maxlen = width;
582
583 /* print the sensors */
584 for (i = 0; i < nelems; i++) {
585 /* skip sensors that don't belong to device 'dvname' */
586 if (strcmp(es[i].dvname, dvname))
587 continue;
588
589 /* skip sensors that were not marked as visible */
590 if (sensors && !es[i].visible)
591 continue;
592
593 /* Do not print invalid sensors if -I is set */
594 if ((flags & ENVSYS_IFLAG) && es[i].invalid)
595 continue;
596
597 (void)printf("%s%*.*s", mydevname ? "" : " ", (int)maxlen,
598 (int)maxlen, es[i].desc);
599
600 if (es[i].invalid) {
601 (void)printf(": %10s\n", invalid);
602 continue;
603 }
604
605 if (strcmp(es[i].type, "Indicator") == 0) {
606
607 (void)printf(": %10s", es[i].cur_value ? "ON" : "OFF");
608
609 /* converts the value to degC or degF */
610 #define CONVERTTEMP(a, b, c) \
611 do { \
612 if (b) \
613 (a) = ((b) / 1000000.0) - 273.15; \
614 if (flags & ENVSYS_FFLAG) { \
615 if (b) \
616 (a) = (9.0 / 5.0) * (a) + 32.0; \
617 (c) = "degF"; \
618 } else \
619 (c) = "degC"; \
620 } while (/* CONSTCOND */ 0)
621
622
623 /* temperatures */
624 } else if (strcmp(es[i].type, "Temperature") == 0) {
625
626 CONVERTTEMP(temp, es[i].cur_value, degrees);
627 (void)printf(": %10.3f %s", temp, degrees);
628
629 if (es[i].critmax_value || es[i].critmin_value)
630 (void)printf(" ");
631
632 if (es[i].critmax_value) {
633 CONVERTTEMP(temp, es[i].critmax_value, degrees);
634 (void)printf("max: %8.3f %s ", temp, degrees);
635 }
636
637 if (es[i].critmin_value) {
638 CONVERTTEMP(temp, es[i].critmin_value, degrees);
639 (void)printf("min: %8.3f %s", temp, degrees);
640 }
641 #undef CONVERTTEMP
642
643 /* fans */
644 } else if (strcmp(es[i].type, "Fan") == 0) {
645
646 (void)printf(": %10u RPM", es[i].cur_value);
647
648 if (es[i].critmax_value || es[i].critmin_value)
649 (void)printf(" ");
650 if (es[i].critmax_value)
651 (void)printf("max: %8u RPM ",
652 es[i].critmax_value);
653 if (es[i].critmin_value)
654 (void)printf("min: %8u RPM",
655 es[i].critmin_value);
656
657 /* integers */
658 } else if (strcmp(es[i].type, "Integer") == 0) {
659
660 (void)printf(": %10d", es[i].cur_value);
661
662 /* drives */
663 } else if (strcmp(es[i].type, "Drive") == 0) {
664
665 (void)printf(": %10s", es[i].drvstate);
666
667 /* Battery state */
668 } else if (strcmp(es[i].type, "Battery state") == 0) {
669
670 (void)printf(": %10s", es[i].battstate);
671
672 /* everything else */
673 } else {
674 const char *type;
675
676 if (strcmp(es[i].type, "Voltage DC") == 0)
677 type = "V";
678 else if (strcmp(es[i].type, "Voltage AC") == 0)
679 type = "VAC";
680 else if (strcmp(es[i].type, "Ampere") == 0)
681 type = "A";
682 else if (strcmp(es[i].type, "Watts") == 0)
683 type = "W";
684 else if (strcmp(es[i].type, "Ohms") == 0)
685 type = "Ohms";
686 else if (strcmp(es[i].type, "Watt hour") == 0)
687 type = "Wh";
688 else if (strcmp(es[i].type, "Ampere hour") == 0)
689 type = "Ah";
690 else
691 type = NULL;
692
693 (void)printf(": %10.3f %s",
694 es[i].cur_value / 1000000.0, type);
695
696 if (es[i].percentage && es[i].max_value) {
697 (void)printf(" (%5.2f%%)",
698 (es[i].cur_value * 100.0) /
699 es[i].max_value);
700 }
701
702 if (es[i].critcap_value) {
703 (void)printf(" critical (%5.2f%%)",
704 (es[i].critcap_value * 100.0) /
705 es[i].max_value);
706 }
707
708 if (es[i].critmax_value || es[i].critmin_value)
709 (void)printf(" ");
710 if (es[i].critmax_value)
711 (void)printf("max: %8.3f %s ",
712 es[i].critmax_value / 1000000.0,
713 type);
714 if (es[i].critmin_value)
715 (void)printf("min: %8.3f %s",
716 es[i].critmin_value / 1000000.0,
717 type);
718
719 }
720
721 (void)printf("\n");
722 }
723 }
724
725 static int
726 usage(void)
727 {
728 (void)fprintf(stderr, "Usage: %s [-DfIlrSx] ", getprogname());
729 (void)fprintf(stderr, "[-c file] [-d device] [-i interval] ");
730 (void)fprintf(stderr, "[-s sensor,...] [-w width]\n");
731 exit(EXIT_FAILURE);
732 /* NOTREACHED */
733 }
734