1 /* $OpenBSD: sensorsd.c,v 1.27 2007/01/06 18:17:06 deraadt Exp $ */ 2 3 /* 4 * Copyright (c) 2003 Henning Brauer <henning@openbsd.org> 5 * Copyright (c) 2005 Matthew Gream <matthew.gream@pobox.com> 6 * 7 * Permission to use, copy, modify, and distribute this software for any 8 * purpose with or without fee is hereby granted, provided that the above 9 * copyright notice and this permission notice appear in all copies. 10 * 11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 */ 19 20 #include <sys/param.h> 21 #include <sys/sysctl.h> 22 #include <sys/sensors.h> 23 24 #include <err.h> 25 #include <errno.h> 26 #include <signal.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <syslog.h> 31 #include <time.h> 32 #include <unistd.h> 33 34 #define RFBUFSIZ 28 /* buffer size for print_sensor */ 35 #define RFBUFCNT 4 /* ring buffers */ 36 #define REPORT_PERIOD 60 /* report every n seconds */ 37 #define CHECK_PERIOD 20 /* check every n seconds */ 38 39 void usage(void); 40 void check_sensors(void); 41 void execute(char *); 42 void report(time_t); 43 static char *print_sensor(enum sensor_type, int64_t); 44 int parse_config(char *); 45 int64_t get_val(char *, int, enum sensor_type); 46 void reparse_cfg(int); 47 48 struct limits_t { 49 TAILQ_ENTRY(limits_t) entries; 50 char dxname[16]; /* device unix name */ 51 int dev; /* device number */ 52 enum sensor_type type; /* sensor type */ 53 int numt; /* sensor number */ 54 int64_t last_val; 55 int64_t lower; /* lower limit */ 56 int64_t upper; /* upper limit */ 57 char *command; /* failure command */ 58 time_t status_changed; 59 enum sensor_status status; /* last status */ 60 enum sensor_status status2; 61 int count; /* stat change counter */ 62 u_int8_t watch; 63 }; 64 65 TAILQ_HEAD(limits, limits_t) limits = TAILQ_HEAD_INITIALIZER(limits); 66 67 char *configfile; 68 volatile sig_atomic_t reload = 0; 69 int debug = 0; 70 71 void 72 usage(void) 73 { 74 extern char *__progname; 75 fprintf(stderr, "usage: %s [-d]\n", __progname); 76 exit(1); 77 } 78 79 int 80 main(int argc, char *argv[]) 81 { 82 struct sensor sensor; 83 struct sensordev sensordev; 84 struct limits_t *limit; 85 size_t slen, sdlen; 86 time_t next_report, last_report = 0, next_check; 87 int mib[5], dev, numt; 88 enum sensor_type type; 89 int sleeptime, sensor_cnt, watch_cnt, ch; 90 91 while ((ch = getopt(argc, argv, "d")) != -1) { 92 switch (ch) { 93 case 'd': 94 debug = 1; 95 break; 96 default: 97 usage(); 98 } 99 } 100 101 mib[0] = CTL_HW; 102 mib[1] = HW_SENSORS; 103 slen = sizeof(sensor); 104 sdlen = sizeof(sensordev); 105 106 sensor_cnt = 0; 107 for (dev = 0; dev < MAXSENSORDEVICES; dev++) { 108 mib[2] = dev; 109 if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) == -1) { 110 if (errno != ENOENT) 111 warn("sysctl"); 112 continue; 113 } 114 for (type = 0; type < SENSOR_MAX_TYPES; type++) { 115 mib[3] = type; 116 for (numt = 0; numt < sensordev.maxnumt[type]; numt++) { 117 mib[4] = numt; 118 if (sysctl(mib, 5, &sensor, &slen, NULL, 0) 119 == -1) { 120 if (errno != ENOENT) 121 warn("sysctl"); 122 continue; 123 } 124 if (sensor.flags & SENSOR_FINVALID) 125 continue; 126 if ((limit = calloc(1, sizeof(struct limits_t))) 127 == NULL) 128 err(1, "calloc"); 129 strlcpy(limit->dxname, sensordev.xname, 130 sizeof(limit->dxname)); 131 limit->dev = dev; 132 limit->type = type; 133 limit->numt = numt; 134 TAILQ_INSERT_TAIL(&limits, limit, entries); 135 sensor_cnt++; 136 } 137 } 138 } 139 140 if (sensor_cnt == 0) 141 errx(1, "no sensors found"); 142 143 openlog("sensorsd", LOG_PID | LOG_NDELAY, LOG_DAEMON); 144 145 if (configfile == NULL) 146 if (asprintf(&configfile, "/etc/sensorsd.conf") == -1) 147 err(1, "out of memory"); 148 if ((watch_cnt = parse_config(configfile)) == -1) 149 errx(1, "error in config file"); 150 151 if (watch_cnt == 0) 152 errx(1, "no watches defined"); 153 154 if (debug == 0 && daemon(0, 0) == -1) 155 err(1, "unable to fork"); 156 157 signal(SIGHUP, reparse_cfg); 158 signal(SIGCHLD, SIG_IGN); 159 160 syslog(LOG_INFO, "startup, %d watches for %d sensors", 161 watch_cnt, sensor_cnt); 162 163 next_check = next_report = time(NULL); 164 165 for (;;) { 166 if (reload) { 167 if ((watch_cnt = parse_config(configfile)) == -1) 168 syslog(LOG_CRIT, "error in config file %s", 169 configfile); 170 else 171 syslog(LOG_INFO, 172 "configuration reloaded, %d watches", 173 watch_cnt); 174 reload = 0; 175 } 176 if (next_check <= time(NULL)) { 177 check_sensors(); 178 next_check = time(NULL) + CHECK_PERIOD; 179 } 180 if (next_report <= time(NULL)) { 181 report(last_report); 182 last_report = next_report; 183 next_report = time(NULL) + REPORT_PERIOD; 184 } 185 if (next_report < next_check) 186 sleeptime = next_report - time(NULL); 187 else 188 sleeptime = next_check - time(NULL); 189 if (sleeptime > 0) 190 sleep(sleeptime); 191 } 192 } 193 194 void 195 check_sensors(void) 196 { 197 struct sensor sensor; 198 struct limits_t *limit; 199 size_t len; 200 int mib[5]; 201 enum sensor_status newstatus; 202 203 mib[0] = CTL_HW; 204 mib[1] = HW_SENSORS; 205 len = sizeof(sensor); 206 207 TAILQ_FOREACH(limit, &limits, entries) 208 if (limit->watch) { 209 mib[2] = limit->dev; 210 mib[3] = limit->type; 211 mib[4] = limit->numt; 212 if (sysctl(mib, 5, &sensor, &len, NULL, 0) == -1) 213 err(1, "sysctl"); 214 215 limit->last_val = sensor.value; 216 newstatus = sensor.status; 217 /* unknown may as well mean producing valid 218 * status had failed so warn about it */ 219 if (newstatus == SENSOR_S_UNKNOWN) 220 newstatus = SENSOR_S_WARN; 221 else if (newstatus == SENSOR_S_UNSPEC) { 222 if (sensor.value > limit->upper || 223 sensor.value < limit->lower) 224 newstatus = SENSOR_S_CRIT; 225 else 226 newstatus = SENSOR_S_OK; 227 } 228 229 if (limit->status != newstatus) { 230 if (newstatus == SENSOR_S_OK) { 231 limit->status2 = 232 limit->status = newstatus; 233 limit->status_changed = time(NULL); 234 } else if (limit->status2 != newstatus) { 235 limit->status2 = newstatus; 236 limit->count = 0; 237 } else if (++limit->count >= 3) { 238 limit->status2 = 239 limit->status = newstatus; 240 limit->status_changed = time(NULL); 241 } 242 } 243 } 244 } 245 246 void 247 execute(char *command) 248 { 249 char *argp[] = {"sh", "-c", command, NULL}; 250 251 switch (fork()) { 252 case -1: 253 syslog(LOG_CRIT, "execute: fork() failed"); 254 break; 255 case 0: 256 execv("/bin/sh", argp); 257 _exit(1); 258 /* NOTREACHED */ 259 default: 260 break; 261 } 262 } 263 264 void 265 report(time_t last_report) 266 { 267 struct limits_t *limit = NULL; 268 269 TAILQ_FOREACH(limit, &limits, entries) { 270 if (limit->status_changed <= last_report) 271 continue; 272 273 syslog(LOG_ALERT, "hw.sensors.%s.%s%d: %s limits, value: %s", 274 limit->dxname, sensor_type_s[limit->type], limit->numt, 275 (limit->status != SENSOR_S_OK) ? "exceed" : "within", 276 print_sensor(limit->type, limit->last_val)); 277 if (limit->command) { 278 int i = 0, n = 0, r; 279 char *cmd = limit->command; 280 char buf[BUFSIZ]; 281 int len = sizeof(buf); 282 283 buf[0] = '\0'; 284 for (i = n = 0; n < len; ++i) { 285 if (cmd[i] == '\0') { 286 buf[n++] = '\0'; 287 break; 288 } 289 if (cmd[i] != '%') { 290 buf[n++] = limit->command[i]; 291 continue; 292 } 293 i++; 294 if (cmd[i] == '\0') { 295 buf[n++] = '\0'; 296 break; 297 } 298 299 switch (cmd[i]) { 300 case 'x': 301 r = snprintf(&buf[n], len - n, "%s", 302 limit->dxname); 303 break; 304 case 't': 305 r = snprintf(&buf[n], len - n, "%s", 306 sensor_type_s[limit->type]); 307 break; 308 case 'n': 309 r = snprintf(&buf[n], len - n, "%d", 310 limit->numt); 311 break; 312 case '2': 313 r = snprintf(&buf[n], len - n, "%s", 314 print_sensor(limit->type, 315 limit->last_val)); 316 break; 317 case '3': 318 r = snprintf(&buf[n], len - n, "%s", 319 print_sensor(limit->type, 320 limit->lower)); 321 break; 322 case '4': 323 r = snprintf(&buf[n], len - n, "%s", 324 print_sensor(limit->type, 325 limit->upper)); 326 break; 327 default: 328 r = snprintf(&buf[n], len - n, "%%%c", 329 cmd[i]); 330 break; 331 } 332 if (r < 0 || (r >= len - n)) { 333 syslog(LOG_CRIT, "could not parse " 334 "command"); 335 return; 336 } 337 if (r > 0) 338 n += r; 339 } 340 if (buf[0]) 341 execute(buf); 342 } 343 } 344 } 345 346 const char *drvstat[] = { 347 NULL, "empty", "ready", "powerup", "online", "idle", "active", 348 "rebuild", "powerdown", "fail", "pfail" 349 }; 350 351 static char * 352 print_sensor(enum sensor_type type, int64_t value) 353 { 354 static char rfbuf[RFBUFCNT][RFBUFSIZ]; /* ring buffer */ 355 static int idx; 356 char *fbuf; 357 358 fbuf = rfbuf[idx++]; 359 if (idx == RFBUFCNT) 360 idx = 0; 361 362 switch (type) { 363 case SENSOR_TEMP: 364 snprintf(fbuf, RFBUFSIZ, "%.2f degC", 365 (value - 273150000) / 1000000.0); 366 break; 367 case SENSOR_FANRPM: 368 snprintf(fbuf, RFBUFSIZ, "%lld RPM", value); 369 break; 370 case SENSOR_VOLTS_DC: 371 snprintf(fbuf, RFBUFSIZ, "%.2f V DC", value / 1000000.0); 372 break; 373 case SENSOR_AMPS: 374 snprintf(fbuf, RFBUFSIZ, "%.2f A", value / 1000000.0); 375 break; 376 case SENSOR_INDICATOR: 377 snprintf(fbuf, RFBUFSIZ, "%s", value? "On" : "Off"); 378 break; 379 case SENSOR_INTEGER: 380 snprintf(fbuf, RFBUFSIZ, "%lld raw", value); 381 break; 382 case SENSOR_PERCENT: 383 snprintf(fbuf, RFBUFSIZ, "%.2f%%", value / 1000.0); 384 break; 385 case SENSOR_LUX: 386 snprintf(fbuf, RFBUFSIZ, "%.2f lx", value / 1000000.0); 387 break; 388 case SENSOR_DRIVE: 389 if (0 < value && value < sizeof(drvstat)/sizeof(drvstat[0])) { 390 snprintf(fbuf, RFBUFSIZ, "%s", drvstat[value]); 391 break; 392 } 393 /* FALLTHROUGH */ 394 default: 395 snprintf(fbuf, RFBUFSIZ, "%lld ???", value); 396 } 397 398 return (fbuf); 399 } 400 401 int 402 parse_config(char *cf) 403 { 404 struct limits_t *p, *next; 405 char *buf = NULL, *ebuf = NULL; 406 char node[48]; 407 char **cfa; 408 int watch_cnt = 0; 409 410 if ((cfa = calloc(2, sizeof(char *))) == NULL) 411 err(1, "calloc"); 412 cfa[0] = cf; 413 cfa[1] = NULL; 414 415 for (p = TAILQ_FIRST(&limits); p != NULL; p = next) { 416 next = TAILQ_NEXT(p, entries); 417 snprintf(node, sizeof(node), "hw.sensors.%s.%s%d", 418 p->dxname, sensor_type_s[p->type], p->numt); 419 if (cgetent(&buf, cfa, node) != 0) 420 p->watch = 0; 421 else { 422 p->watch = 1; 423 watch_cnt++; 424 if (cgetstr(buf, "low", &ebuf) < 0) 425 ebuf = NULL; 426 p->lower = get_val(ebuf, 0, p->type); 427 if (cgetstr(buf, "high", &ebuf) < 0) 428 ebuf = NULL; 429 p->upper = get_val(ebuf, 1, p->type); 430 if (cgetstr(buf, "command", &ebuf) < 0) 431 ebuf = NULL; 432 if (ebuf) 433 asprintf(&(p->command), "%s", ebuf); 434 free(buf); 435 buf = NULL; 436 } 437 } 438 free(cfa); 439 return (watch_cnt); 440 } 441 442 int64_t 443 get_val(char *buf, int upper, enum sensor_type type) 444 { 445 double val; 446 int64_t rval = 0; 447 char *p; 448 449 if (buf == NULL) { 450 if (upper) 451 return (LLONG_MAX); 452 else 453 return (LLONG_MIN); 454 } 455 456 val = strtod(buf, &p); 457 if (buf == p) 458 err(1, "incorrect value: %s", buf); 459 460 switch(type) { 461 case SENSOR_TEMP: 462 switch(*p) { 463 case 'C': 464 printf("C"); 465 rval = (val + 273.16) * 1000 * 1000; 466 break; 467 case 'F': 468 printf("F"); 469 rval = ((val - 32.0) / 9 * 5 + 273.16) * 1000 * 1000; 470 break; 471 default: 472 errx(1, "unknown unit %s for temp sensor", p); 473 } 474 break; 475 case SENSOR_FANRPM: 476 rval = val; 477 break; 478 case SENSOR_VOLTS_DC: 479 if (*p != 'V') 480 errx(1, "unknown unit %s for voltage sensor", p); 481 rval = val * 1000 * 1000; 482 break; 483 case SENSOR_PERCENT: 484 rval = val * 1000.0; 485 break; 486 case SENSOR_INDICATOR: 487 case SENSOR_INTEGER: 488 case SENSOR_DRIVE: 489 rval = val; 490 break; 491 case SENSOR_LUX: 492 rval = val * 1000 * 1000; 493 break; 494 default: 495 errx(1, "unsupported sensor type"); 496 /* not reached */ 497 } 498 free(buf); 499 return (rval); 500 } 501 502 /* ARGSUSED */ 503 void 504 reparse_cfg(int signo) 505 { 506 reload = 1; 507 } 508