1 /* $NetBSD: envstat.c,v 1.57 2007/11/03 23:05:22 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.57 2007/11/03 23:05:22 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 battcap[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 capacity string */ 435 obj1 = prop_dictionary_get(obj, "battery-capacity"); 436 if (obj1) 437 (void)strlcpy(gesen[gnelems].battcap, 438 prop_string_cstring_nocopy(obj1), 439 sizeof(gesen[gnelems].battcap)); 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 /* 606 * Indicator and Battery charge sensors. 607 */ 608 if ((strcmp(es[i].type, "Indicator") == 0) || 609 (strcmp(es[i].type, "Battery charge") == 0)) { 610 611 (void)printf(": %10s", es[i].cur_value ? "ON" : "OFF"); 612 613 /* converts the value to degC or degF */ 614 #define CONVERTTEMP(a, b, c) \ 615 do { \ 616 if (b) \ 617 (a) = ((b) / 1000000.0) - 273.15; \ 618 if (flags & ENVSYS_FFLAG) { \ 619 if (b) \ 620 (a) = (9.0 / 5.0) * (a) + 32.0; \ 621 (c) = "degF"; \ 622 } else \ 623 (c) = "degC"; \ 624 } while (/* CONSTCOND */ 0) 625 626 627 /* temperatures */ 628 } else if (strcmp(es[i].type, "Temperature") == 0) { 629 630 CONVERTTEMP(temp, es[i].cur_value, degrees); 631 (void)printf(": %10.3f %s", temp, degrees); 632 633 if (es[i].critmax_value || es[i].critmin_value) 634 (void)printf(" "); 635 636 if (es[i].critmax_value) { 637 CONVERTTEMP(temp, es[i].critmax_value, degrees); 638 (void)printf("max: %8.3f %s ", temp, degrees); 639 } 640 641 if (es[i].critmin_value) { 642 CONVERTTEMP(temp, es[i].critmin_value, degrees); 643 (void)printf("min: %8.3f %s", temp, degrees); 644 } 645 #undef CONVERTTEMP 646 647 /* fans */ 648 } else if (strcmp(es[i].type, "Fan") == 0) { 649 650 (void)printf(": %10u RPM", es[i].cur_value); 651 652 if (es[i].critmax_value || es[i].critmin_value) 653 (void)printf(" "); 654 if (es[i].critmax_value) 655 (void)printf("max: %8u RPM ", 656 es[i].critmax_value); 657 if (es[i].critmin_value) 658 (void)printf("min: %8u RPM", 659 es[i].critmin_value); 660 661 /* integers */ 662 } else if (strcmp(es[i].type, "Integer") == 0) { 663 664 (void)printf(": %10d", es[i].cur_value); 665 666 /* drives */ 667 } else if (strcmp(es[i].type, "Drive") == 0) { 668 669 (void)printf(": %10s", es[i].drvstate); 670 671 /* Battery capacity */ 672 } else if (strcmp(es[i].type, "Battery capacity") == 0) { 673 674 (void)printf(": %10s", es[i].battcap); 675 676 /* everything else */ 677 } else { 678 const char *type; 679 680 if (strcmp(es[i].type, "Voltage DC") == 0) 681 type = "V"; 682 else if (strcmp(es[i].type, "Voltage AC") == 0) 683 type = "VAC"; 684 else if (strcmp(es[i].type, "Ampere") == 0) 685 type = "A"; 686 else if (strcmp(es[i].type, "Watts") == 0) 687 type = "W"; 688 else if (strcmp(es[i].type, "Ohms") == 0) 689 type = "Ohms"; 690 else if (strcmp(es[i].type, "Watt hour") == 0) 691 type = "Wh"; 692 else if (strcmp(es[i].type, "Ampere hour") == 0) 693 type = "Ah"; 694 else 695 type = NULL; 696 697 (void)printf(": %10.3f %s", 698 es[i].cur_value / 1000000.0, type); 699 700 if (es[i].percentage && es[i].max_value) { 701 (void)printf(" (%5.2f%%)", 702 (es[i].cur_value * 100.0) / 703 es[i].max_value); 704 } 705 706 if (es[i].critcap_value) { 707 (void)printf(" critical (%5.2f%%)", 708 (es[i].critcap_value * 100.0) / 709 es[i].max_value); 710 } 711 712 if (es[i].critmax_value || es[i].critmin_value) 713 (void)printf(" "); 714 if (es[i].critmax_value) 715 (void)printf("max: %8.3f %s ", 716 es[i].critmax_value / 1000000.0, 717 type); 718 if (es[i].critmin_value) 719 (void)printf("min: %8.3f %s", 720 es[i].critmin_value / 1000000.0, 721 type); 722 723 } 724 725 (void)printf("\n"); 726 } 727 } 728 729 static int 730 usage(void) 731 { 732 (void)fprintf(stderr, "Usage: %s [-DfIlrSx] ", getprogname()); 733 (void)fprintf(stderr, "[-c file] [-d device] [-i interval] "); 734 (void)fprintf(stderr, "[-s sensor,...] [-w width]\n"); 735 exit(EXIT_FAILURE); 736 /* NOTREACHED */ 737 } 738