Hardware sensors framework originally developed in OpenBSD and ported to
[dragonfly.git] / usr.sbin / sensorsd / sensorsd.c
1 /* $OpenBSD: sensorsd.c,v 1.34 2007/08/14 17:10:02 cnst Exp $ */
2 /* $DragonFly: src/usr.sbin/sensorsd/sensorsd.c,v 1.1 2007/10/02 12:57:01 hasso Exp $ */
3
4 /*
5  * Copyright (c) 2003 Henning Brauer <henning@openbsd.org>
6  * Copyright (c) 2005 Matthew Gream <matthew.gream@pobox.com>
7  * Copyright (c) 2006 Constantine A. Murenin <cnst+openbsd@bugmail.mojo.ru>
8  *
9  * Permission to use, copy, modify, and distribute this software for any
10  * purpose with or without fee is hereby granted, provided that the above
11  * copyright notice and this permission notice appear in all copies.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20  */
21
22 #include <sys/cdefs.h>
23 #include <sys/param.h>
24 #include <sys/sysctl.h>
25 #include <sys/sensors.h>
26
27 #include <err.h>
28 #include <errno.h>
29 #include <signal.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <syslog.h>
34 #include <time.h>
35 #include <unistd.h>
36
37 #define RFBUFSIZ        28      /* buffer size for print_sensor */
38 #define RFBUFCNT        4       /* ring buffers */
39 #define REPORT_PERIOD   60      /* report every n seconds */
40 #define CHECK_PERIOD    20      /* check every n seconds */
41
42 enum sensorsd_s_status {
43         SENSORSD_S_UNSPEC,      /* status is unspecified */
44         SENSORSD_S_INVALID,     /* status is invalid, per SENSOR_FINVALID */
45         SENSORSD_S_WITHIN,      /* status is within limits */
46         SENSORSD_S_OUTSIDE      /* status is outside limits */
47 };
48
49 struct limits_t {
50         TAILQ_ENTRY(limits_t)   entries;
51         enum sensor_type        type;           /* sensor type */
52         int                     numt;           /* sensor number */
53         int64_t                 last_val;
54         int64_t                 lower;          /* lower limit */
55         int64_t                 upper;          /* upper limit */
56         char                    *command;       /* failure command */
57         time_t                  astatus_changed;
58         time_t                  ustatus_changed;
59         enum sensor_status      astatus;        /* last automatic status */
60         enum sensor_status      astatus2;
61         enum sensorsd_s_status  ustatus;        /* last user-limit status */
62         enum sensorsd_s_status  ustatus2;
63         int                     acount;         /* stat change counter */
64         int                     ucount;         /* stat change counter */
65         u_int8_t                flags;          /* sensorsd limit flags */
66 #define SENSORSD_L_USERLIMIT            0x0001  /* user specified limit */
67 #define SENSORSD_L_ISTATUS              0x0002  /* ignore automatic status */
68 };
69
70 struct sdlim_t {
71         TAILQ_ENTRY(sdlim_t)    entries;
72         char                    dxname[16];     /* device unix name */
73         int                     dev;            /* device number */
74         int                     sensor_cnt;
75         TAILQ_HEAD(, limits_t)  limits;
76 };
77
78 void             usage(void);
79 struct sdlim_t  *create_sdlim(struct sensordev *);
80 void             check(void);
81 void             check_sdlim(struct sdlim_t *);
82 void             execute(char *);
83 void             report(time_t);
84 void             report_sdlim(struct sdlim_t *, time_t);
85 static char     *print_sensor(enum sensor_type, int64_t);
86 void             parse_config(char *);
87 void             parse_config_sdlim(struct sdlim_t *, char **);
88 int64_t          get_val(char *, int, enum sensor_type);
89 void             reparse_cfg(int);
90
91 TAILQ_HEAD(, sdlim_t) sdlims = TAILQ_HEAD_INITIALIZER(sdlims);
92
93 char                     *configfile;
94 volatile sig_atomic_t     reload = 0;
95 int                       debug = 0;
96
97 void
98 usage(void)
99 {
100         extern char *__progname;
101         fprintf(stderr, "usage: %s [-d]\n", __progname);
102         exit(1);
103 }
104
105 int
106 main(int argc, char *argv[])
107 {
108         struct sensordev sensordev;
109         struct sdlim_t  *sdlim;
110         size_t           sdlen = sizeof(sensordev);
111         time_t           next_report, last_report = 0, next_check;
112         int              mib[3], dev;
113         int              sleeptime, sensor_cnt = 0, ch;
114
115         while ((ch = getopt(argc, argv, "d")) != -1) {
116                 switch (ch) {
117                 case 'd':
118                         debug = 1;
119                         break;
120                 default:
121                         usage();
122                 }
123         }
124
125         mib[0] = CTL_HW;
126         mib[1] = HW_SENSORS;
127
128         for (dev = 0; dev < MAXSENSORDEVICES; dev++) {
129                 mib[2] = dev;
130                 if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) == -1) {
131                         if (errno != ENOENT)
132                                 warn("sysctl");
133                         continue;
134                 }
135                 sdlim = create_sdlim(&sensordev);
136                 TAILQ_INSERT_TAIL(&sdlims, sdlim, entries);
137                 sensor_cnt += sdlim->sensor_cnt;
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         parse_config(configfile);
149
150         if (debug == 0 && daemon(0, 0) == -1)
151                 err(1, "unable to fork");
152
153         signal(SIGHUP, reparse_cfg);
154         signal(SIGCHLD, SIG_IGN);
155
156         syslog(LOG_INFO, "startup, system has %d sensors", sensor_cnt);
157
158         next_check = next_report = time(NULL);
159
160         for (;;) {
161                 if (reload) {
162                         parse_config(configfile);
163                         syslog(LOG_INFO, "configuration reloaded");
164                         reload = 0;
165                 }
166                 if (next_check <= time(NULL)) {
167                         check();
168                         next_check = time(NULL) + CHECK_PERIOD;
169                 }
170                 if (next_report <= time(NULL)) {
171                         report(last_report);
172                         last_report = next_report;
173                         next_report = time(NULL) + REPORT_PERIOD;
174                 }
175                 if (next_report < next_check)
176                         sleeptime = next_report - time(NULL);
177                 else
178                         sleeptime = next_check - time(NULL);
179                 if (sleeptime > 0)
180                         sleep(sleeptime);
181         }
182 }
183
184 struct sdlim_t *
185 create_sdlim(struct sensordev *snsrdev)
186 {
187         struct sensor    sensor;
188         struct sdlim_t  *sdlim;
189         struct limits_t *limit;
190         size_t           slen = sizeof(sensor);
191         int              mib[5], numt;
192         enum sensor_type type;
193
194         if ((sdlim = calloc(1, sizeof(struct sdlim_t))) == NULL)
195                 err(1, "calloc");
196
197         strlcpy(sdlim->dxname, snsrdev->xname, sizeof(sdlim->dxname));
198
199         mib[0] = CTL_HW;
200         mib[1] = HW_SENSORS;
201         mib[2] = sdlim->dev = snsrdev->num;
202
203         TAILQ_INIT(&sdlim->limits);
204
205         for (type = 0; type < SENSOR_MAX_TYPES; type++) {
206                 mib[3] = type;
207                 for (numt = 0; numt < snsrdev->maxnumt[type]; numt++) {
208                         mib[4] = numt;
209                         if (sysctl(mib, 5, &sensor, &slen, NULL, 0) == -1) {
210                                 if (errno != ENOENT)
211                                         warn("sysctl");
212                                 continue;
213                         }
214                         if ((limit = calloc(1, sizeof(struct limits_t))) ==
215                             NULL)
216                                 err(1, "calloc");
217                         limit->type = type;
218                         limit->numt = numt;
219                         TAILQ_INSERT_TAIL(&sdlim->limits, limit, entries);
220                         sdlim->sensor_cnt++;
221                 }
222         }
223
224         return (sdlim);
225 }
226
227 void
228 check(void)
229 {
230         struct sdlim_t  *sdlim;
231
232         TAILQ_FOREACH(sdlim, &sdlims, entries)
233                 check_sdlim(sdlim);
234 }
235
236 void
237 check_sdlim(struct sdlim_t *sdlim)
238 {
239         struct sensor            sensor;
240         struct limits_t         *limit;
241         size_t                   len;
242         int                      mib[5];
243
244         mib[0] = CTL_HW;
245         mib[1] = HW_SENSORS;
246         mib[2] = sdlim->dev;
247         len = sizeof(sensor);
248
249         TAILQ_FOREACH(limit, &sdlim->limits, entries) {
250                 if ((limit->flags & SENSORSD_L_ISTATUS) &&
251                     !(limit->flags & SENSORSD_L_USERLIMIT)) 
252                         continue;
253
254                 mib[3] = limit->type;
255                 mib[4] = limit->numt;
256                 if (sysctl(mib, 5, &sensor, &len, NULL, 0) == -1)
257                         err(1, "sysctl");
258
259                 if (!(limit->flags & SENSORSD_L_ISTATUS)) {
260                         enum sensor_status      newastatus = sensor.status;
261
262                         if (limit->astatus != newastatus) {
263                                 if (limit->astatus2 != newastatus) {
264                                         limit->astatus2 = newastatus;
265                                         limit->acount = 0;
266                                 } else if (++limit->acount >= 3) {
267                                         limit->last_val = sensor.value;
268                                         limit->astatus2 =
269                                             limit->astatus = newastatus;
270                                         limit->astatus_changed = time(NULL);
271                                 }
272                         }
273                 }
274         
275                 if (limit->flags & SENSORSD_L_USERLIMIT) {
276                         enum sensorsd_s_status   newustatus;
277
278                         if (sensor.flags & SENSOR_FINVALID)
279                                 newustatus = SENSORSD_S_INVALID;
280                         else if (sensor.value > limit->upper ||
281                                 sensor.value < limit->lower)
282                                 newustatus = SENSORSD_S_OUTSIDE;
283                         else
284                                 newustatus = SENSORSD_S_WITHIN;
285
286                         if (limit->ustatus != newustatus) {
287                                 if (limit->ustatus2 != newustatus) {
288                                         limit->ustatus2 = newustatus;
289                                         limit->ucount = 0;
290                                 } else if (++limit->ucount >= 3) {
291                                         limit->last_val = sensor.value;
292                                         limit->ustatus2 =
293                                             limit->ustatus = newustatus;
294                                         limit->ustatus_changed = time(NULL);
295                                 }
296                         }
297                 }
298         }
299 }
300
301 void
302 execute(char *command)
303 {
304         char *argp[] = {"sh", "-c", command, NULL};
305
306         switch (fork()) {
307         case -1:
308                 syslog(LOG_CRIT, "execute: fork() failed");
309                 break;
310         case 0:
311                 execv("/bin/sh", argp);
312                 _exit(1);
313                 /* NOTREACHED */
314         default:
315                 break;
316         }
317 }
318
319 void
320 report(time_t last_report)
321 {
322         struct sdlim_t  *sdlim;
323  
324         TAILQ_FOREACH(sdlim, &sdlims, entries)
325                 report_sdlim(sdlim, last_report);
326 }
327
328 void
329 report_sdlim(struct sdlim_t *sdlim, time_t last_report)
330 {
331         struct limits_t *limit;
332
333         TAILQ_FOREACH(limit, &sdlim->limits, entries) {
334                 if ((limit->astatus_changed <= last_report) &&
335                     (limit->ustatus_changed <= last_report))
336                         continue;
337
338                 if (limit->astatus_changed > last_report) {
339                         const char *as = NULL;
340
341                         switch (limit->astatus) {
342                         case SENSOR_S_UNSPEC:
343                                 as = "";
344                                 break;
345                         case SENSOR_S_OK:
346                                 as = ", OK";
347                                 break;
348                         case SENSOR_S_WARN:
349                                 as = ", WARN";
350                                 break;
351                         case SENSOR_S_CRIT:
352                                 as = ", CRITICAL";
353                                 break;
354                         case SENSOR_S_UNKNOWN:
355                                 as = ", UNKNOWN";
356                                 break;
357                         }
358                         syslog(LOG_ALERT, "%s.%s%d: %s%s",
359                             sdlim->dxname, sensor_type_s[limit->type],
360                             limit->numt,
361                             print_sensor(limit->type, limit->last_val), as);
362                 }
363
364                 if (limit->ustatus_changed > last_report) {
365                         char us[BUFSIZ];
366
367                         switch (limit->ustatus) {
368                         case SENSORSD_S_UNSPEC:
369                                 snprintf(us, sizeof(us),
370                                     "ustatus uninitialised");
371                                 break;
372                         case SENSORSD_S_INVALID:
373                                 snprintf(us, sizeof(us), "marked invalid");
374                                 break;
375                         case SENSORSD_S_WITHIN:
376                                 snprintf(us, sizeof(us), "within limits: %s",
377                                     print_sensor(limit->type, limit->last_val));
378                                 break;
379                         case SENSORSD_S_OUTSIDE:
380                                 snprintf(us, sizeof(us), "exceeds limits: %s",
381                                     print_sensor(limit->type, limit->last_val));
382                                 break;
383                         }
384                         syslog(LOG_ALERT, "%s.%s%d: %s",
385                             sdlim->dxname, sensor_type_s[limit->type],
386                             limit->numt, us);
387                 }
388
389                 if (limit->command) {
390                         int i = 0, n = 0, r;
391                         char *cmd = limit->command;
392                         char buf[BUFSIZ];
393                         int len = sizeof(buf);
394
395                         buf[0] = '\0';
396                         for (i = n = 0; n < len; ++i) {
397                                 if (cmd[i] == '\0') {
398                                         buf[n++] = '\0';
399                                         break;
400                                 }
401                                 if (cmd[i] != '%') {
402                                         buf[n++] = limit->command[i];
403                                         continue;
404                                 }
405                                 i++;
406                                 if (cmd[i] == '\0') {
407                                         buf[n++] = '\0';
408                                         break;
409                                 }
410
411                                 switch (cmd[i]) {
412                                 case 'x':
413                                         r = snprintf(&buf[n], len - n, "%s",
414                                             sdlim->dxname);
415                                         break;
416                                 case 't':
417                                         r = snprintf(&buf[n], len - n, "%s",
418                                             sensor_type_s[limit->type]);
419                                         break;
420                                 case 'n':
421                                         r = snprintf(&buf[n], len - n, "%d",
422                                             limit->numt);
423                                         break;
424                                 case '2':
425                                         r = snprintf(&buf[n], len - n, "%s",
426                                             print_sensor(limit->type,
427                                             limit->last_val));
428                                         break;
429                                 case '3':
430                                         r = snprintf(&buf[n], len - n, "%s",
431                                             print_sensor(limit->type,
432                                             limit->lower));
433                                         break;
434                                 case '4':
435                                         r = snprintf(&buf[n], len - n, "%s",
436                                             print_sensor(limit->type,
437                                             limit->upper));
438                                         break;
439                                 default:
440                                         r = snprintf(&buf[n], len - n, "%%%c",
441                                             cmd[i]);
442                                         break;
443                                 }
444                                 if (r < 0 || (r >= len - n)) {
445                                         syslog(LOG_CRIT, "could not parse "
446                                             "command");
447                                         return;
448                                 }
449                                 if (r > 0)
450                                         n += r;
451                         }
452                         if (buf[0])
453                                 execute(buf);
454                 }
455         }
456 }
457
458 const char *drvstat[] = {
459         NULL, "empty", "ready", "powerup", "online", "idle", "active",
460         "rebuild", "powerdown", "fail", "pfail"
461 };
462
463 static char *
464 print_sensor(enum sensor_type type, int64_t value)
465 {
466         static char      rfbuf[RFBUFCNT][RFBUFSIZ];     /* ring buffer */
467         static int       idx;
468         char            *fbuf;
469
470         fbuf = rfbuf[idx++];
471         if (idx == RFBUFCNT)
472                 idx = 0;
473
474         switch (type) {
475         case SENSOR_TEMP:
476                 snprintf(fbuf, RFBUFSIZ, "%.2f degC",
477                     (value - 273150000) / 1000000.0);
478                 break;
479         case SENSOR_FANRPM:
480                 snprintf(fbuf, RFBUFSIZ, "%lld RPM", value);
481                 break;
482         case SENSOR_VOLTS_DC:
483                 snprintf(fbuf, RFBUFSIZ, "%.2f V DC", value / 1000000.0);
484                 break;
485         case SENSOR_AMPS:
486                 snprintf(fbuf, RFBUFSIZ, "%.2f A", value / 1000000.0);
487                 break;
488         case SENSOR_WATTHOUR:
489                 snprintf(fbuf, RFBUFSIZ, "%.2f Wh", value / 1000000.0);
490                 break;
491         case SENSOR_AMPHOUR:
492                 snprintf(fbuf, RFBUFSIZ, "%.2f Ah", value / 1000000.0);
493                 break;
494         case SENSOR_INDICATOR:
495                 snprintf(fbuf, RFBUFSIZ, "%s", value? "On" : "Off");
496                 break;
497         case SENSOR_INTEGER:
498                 snprintf(fbuf, RFBUFSIZ, "%lld", value);
499                 break;
500         case SENSOR_PERCENT:
501                 snprintf(fbuf, RFBUFSIZ, "%.2f%%", value / 1000.0);
502                 break;
503         case SENSOR_LUX:
504                 snprintf(fbuf, RFBUFSIZ, "%.2f lx", value / 1000000.0);
505                 break;
506         case SENSOR_DRIVE:
507                 if (0 < value && value < sizeof(drvstat)/sizeof(drvstat[0]))
508                         snprintf(fbuf, RFBUFSIZ, "%s", drvstat[value]);
509                 else
510                         snprintf(fbuf, RFBUFSIZ, "%lld ???", value);
511                 break;
512         case SENSOR_TIMEDELTA:
513                 snprintf(fbuf, RFBUFSIZ, "%.6f secs", value / 1000000000.0);
514                 break;
515         default:
516                 snprintf(fbuf, RFBUFSIZ, "%lld ???", value);
517         }
518
519         return (fbuf);
520 }
521
522 void
523 parse_config(char *cf)
524 {
525         struct sdlim_t   *sdlim;
526         char            **cfa;
527
528         if ((cfa = calloc(2, sizeof(char *))) == NULL)
529                 err(1, "calloc");
530         cfa[0] = cf;
531         cfa[1] = NULL;
532
533         TAILQ_FOREACH(sdlim, &sdlims, entries)
534                 parse_config_sdlim(sdlim, cfa);
535         free(cfa);
536 }
537
538 void
539 parse_config_sdlim(struct sdlim_t *sdlim, char **cfa)
540 {
541         struct limits_t  *p;
542         char             *buf = NULL, *ebuf = NULL;
543         char              node[48];
544
545         TAILQ_FOREACH(p, &sdlim->limits, entries) {
546                 snprintf(node, sizeof(node), "hw.sensors.%s.%s%d", 
547                     sdlim->dxname, sensor_type_s[p->type], p->numt);
548                 p->flags = 0;
549                 if (cgetent(&buf, cfa, node) != 0)
550                         if (cgetent(&buf, cfa, sensor_type_s[p->type]) != 0)
551                                 continue;
552                 if (cgetcap(buf, "istatus", ':'))
553                         p->flags |= SENSORSD_L_ISTATUS;
554                 if (cgetstr(buf, "low", &ebuf) < 0)
555                         ebuf = NULL;
556                 p->lower = get_val(ebuf, 0, p->type);
557                 if (cgetstr(buf, "high", &ebuf) < 0)
558                         ebuf = NULL;
559                 p->upper = get_val(ebuf, 1, p->type);
560                 if (cgetstr(buf, "command", &ebuf) < 0)
561                         ebuf = NULL;
562                 if (ebuf)
563                         asprintf(&(p->command), "%s", ebuf);
564                 free(buf);
565                 buf = NULL;
566                 if (p->lower != LLONG_MIN || p->upper != LLONG_MAX)
567                         p->flags |= SENSORSD_L_USERLIMIT;
568         }
569 }
570
571 int64_t
572 get_val(char *buf, int upper, enum sensor_type type)
573 {
574         double   val;
575         int64_t  rval = 0;
576         char    *p;
577
578         if (buf == NULL) {
579                 if (upper)
580                         return (LLONG_MAX);
581                 else
582                         return (LLONG_MIN);
583         }
584
585         val = strtod(buf, &p);
586         if (buf == p)
587                 err(1, "incorrect value: %s", buf);
588
589         switch(type) {
590         case SENSOR_TEMP:
591                 switch(*p) {
592                 case 'C':
593                         printf("C");
594                         rval = (val + 273.16) * 1000 * 1000;
595                         break;
596                 case 'F':
597                         printf("F");
598                         rval = ((val - 32.0) / 9 * 5 + 273.16) * 1000 * 1000;
599                         break;
600                 default:
601                         errx(1, "unknown unit %s for temp sensor", p);
602                 }
603                 break;
604         case SENSOR_FANRPM:
605                 rval = val;
606                 break;
607         case SENSOR_VOLTS_DC:
608                 if (*p != 'V')
609                         errx(1, "unknown unit %s for voltage sensor", p);
610                 rval = val * 1000 * 1000;
611                 break;
612         case SENSOR_PERCENT:
613                 rval = val * 1000.0;
614                 break;
615         case SENSOR_INDICATOR:
616         case SENSOR_INTEGER:
617         case SENSOR_DRIVE:
618                 rval = val;
619                 break;
620         case SENSOR_AMPS:
621         case SENSOR_WATTHOUR:
622         case SENSOR_AMPHOUR:
623         case SENSOR_LUX:
624                 rval = val * 1000 * 1000;
625                 break;
626         case SENSOR_TIMEDELTA:
627                 rval = val * 1000 * 1000 * 1000;
628                 break;
629         default:
630                 errx(1, "unsupported sensor type");
631                 /* not reached */
632         }
633         free(buf);
634         return (rval);
635 }
636
637 /* ARGSUSED */
638 void
639 reparse_cfg(int signo)
640 {
641         reload = 1;
642 }