/* $OpenBSD: sensorsd.c,v 1.67 2020/07/22 15:33:49 bluhm Exp $ */ /* * Copyright (c) 2003 Henning Brauer * Copyright (c) 2005 Matthew Gream * Copyright (c) 2006 Constantine A. Murenin * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define RFBUFSIZ 28 /* buffer size for print_sensor */ #define RFBUFCNT 4 /* ring buffers */ #define CHECK_PERIOD 20 /* check every n seconds */ enum sensorsd_s_status { SENSORSD_S_UNSPEC, /* status is unspecified */ SENSORSD_S_INVALID, /* status is invalid, per SENSOR_FINVALID */ SENSORSD_S_WITHIN, /* status is within limits */ SENSORSD_S_ABOVE, /* status is above the higher limit */ SENSORSD_S_BELOW /* status is below the lower limit */ }; struct limits_t { TAILQ_ENTRY(limits_t) entries; enum sensor_type type; /* sensor type */ int numt; /* sensor number */ int64_t last_val; int64_t lower; /* lower limit */ int64_t upper; /* upper limit */ char *command; /* failure command */ time_t astatus_changed; time_t ustatus_changed; enum sensor_status astatus; /* last automatic status */ enum sensor_status astatus2; enum sensorsd_s_status ustatus; /* last user-limit status */ enum sensorsd_s_status ustatus2; int acount; /* stat change counter */ int ucount; /* stat change counter */ u_int8_t flags; /* sensorsd limit flags */ #define SENSORSD_L_USERLIMIT 0x0001 /* user specified limit */ #define SENSORSD_L_ISTATUS 0x0002 /* ignore automatic status */ }; struct sdlim_t { TAILQ_ENTRY(sdlim_t) entries; char dxname[16]; /* device unix name */ int dev; /* device number */ int sensor_cnt; TAILQ_HEAD(, limits_t) limits; }; void usage(void); void create(void); struct sdlim_t *create_sdlim(struct sensordev *); void destroy_sdlim(struct sdlim_t *); void check(time_t); void check_sdlim(struct sdlim_t *, time_t); void execute(char *); void report(time_t); void report_sdlim(struct sdlim_t *, time_t); static char *print_sensor(enum sensor_type, int64_t); void parse_config(char *); void parse_config_sdlim(struct sdlim_t *, char *); int64_t get_val(char *, int, enum sensor_type); void reparse_cfg(int); TAILQ_HEAD(sdlimhead_t, sdlim_t); struct sdlimhead_t sdlims = TAILQ_HEAD_INITIALIZER(sdlims); char *configfile, *configdb; volatile sig_atomic_t reload = 0; int debug = 0; void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-d] [-c check] [-f file]\n", __progname); exit(1); } int main(int argc, char *argv[]) { time_t last_report = 0, this_check; int ch, check_period = CHECK_PERIOD; const char *errstr; while ((ch = getopt(argc, argv, "c:df:")) != -1) { switch (ch) { case 'c': check_period = strtonum(optarg, 1, 600, &errstr); if (errstr) errx(1, "check %s", errstr); break; case 'd': debug = 1; break; case 'f': configfile = realpath(optarg, NULL); if (configfile == NULL) err(1, "configuration file %s", optarg); break; default: usage(); } } argc -= optind; argv += optind; if (argc > 0) usage(); if (configfile == NULL) if (asprintf(&configfile, "/etc/sensorsd.conf") == -1) err(1, "out of memory"); if (asprintf(&configdb, "%s.db", configfile) == -1) err(1, "out of memory"); chdir("/"); if (unveil(configfile, "r") == -1) err(1, "unveil"); if (unveil(configdb, "r") == -1) err(1, "unveil"); if (unveil("/", "x") == -1) err(1, "unveil"); if (pledge("stdio rpath proc exec", NULL) == -1) err(1, "pledge"); openlog("sensorsd", LOG_PID | LOG_NDELAY, LOG_DAEMON); create(); parse_config(configfile); if (debug == 0 && daemon(1, 0) == -1) err(1, "unable to fork"); signal(SIGHUP, reparse_cfg); signal(SIGCHLD, SIG_IGN); for (;;) { if (reload) { parse_config(configfile); syslog(LOG_INFO, "configuration reloaded"); reload = 0; } this_check = time(NULL); if (!(last_report < this_check)) this_check = last_report + 1; check(this_check); report(last_report); last_report = this_check; sleep(check_period); } } void create(void) { struct sensordev sensordev; struct sdlim_t *sdlim; size_t sdlen = sizeof(sensordev); int mib[3], dev, sensor_cnt = 0; mib[0] = CTL_HW; mib[1] = HW_SENSORS; for (dev = 0; ; dev++) { mib[2] = dev; if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) == -1) { if (errno == ENXIO) continue; if (errno == ENOENT) break; warn("sysctl"); } sdlim = create_sdlim(&sensordev); TAILQ_INSERT_TAIL(&sdlims, sdlim, entries); sensor_cnt += sdlim->sensor_cnt; } syslog(LOG_INFO, "startup, system has %d sensors", sensor_cnt); } struct sdlim_t * create_sdlim(struct sensordev *snsrdev) { struct sensor sensor; struct sdlim_t *sdlim; struct limits_t *limit; size_t slen = sizeof(sensor); int mib[5], numt; enum sensor_type type; if ((sdlim = calloc(1, sizeof(struct sdlim_t))) == NULL) err(1, "calloc"); strlcpy(sdlim->dxname, snsrdev->xname, sizeof(sdlim->dxname)); mib[0] = CTL_HW; mib[1] = HW_SENSORS; mib[2] = sdlim->dev = snsrdev->num; TAILQ_INIT(&sdlim->limits); for (type = 0; type < SENSOR_MAX_TYPES; type++) { mib[3] = type; for (numt = 0; numt < snsrdev->maxnumt[type]; numt++) { mib[4] = numt; if (sysctl(mib, 5, &sensor, &slen, NULL, 0) == -1) { if (errno != ENOENT) warn("sysctl"); continue; } if ((limit = calloc(1, sizeof(struct limits_t))) == NULL) err(1, "calloc"); limit->type = type; limit->numt = numt; TAILQ_INSERT_TAIL(&sdlim->limits, limit, entries); sdlim->sensor_cnt++; } } return (sdlim); } void destroy_sdlim(struct sdlim_t *sdlim) { struct limits_t *limit; while ((limit = TAILQ_FIRST(&sdlim->limits)) != NULL) { TAILQ_REMOVE(&sdlim->limits, limit, entries); free(limit->command); free(limit); } free(sdlim); } void check(time_t this_check) { struct sensordev sensordev; struct sdlim_t *sdlim, *next; int mib[3]; int h, t, i; size_t sdlen = sizeof(sensordev); if (TAILQ_EMPTY(&sdlims)) { h = 0; t = -1; } else { h = TAILQ_FIRST(&sdlims)->dev; t = TAILQ_LAST(&sdlims, sdlimhead_t)->dev; } sdlim = TAILQ_FIRST(&sdlims); mib[0] = CTL_HW; mib[1] = HW_SENSORS; /* look ahead for 4 more sensordevs */ for (i = h; i <= t + 4; i++) { if (sdlim != NULL && i > sdlim->dev) sdlim = TAILQ_NEXT(sdlim, entries); if (sdlim == NULL && i <= t) syslog(LOG_ALERT, "inconsistent sdlim logic"); mib[2] = i; if (sysctl(mib, 3, &sensordev, &sdlen, NULL, 0) == -1) { if (errno != ENOENT) warn("sysctl"); if (sdlim != NULL && i == sdlim->dev) { next = TAILQ_NEXT(sdlim, entries); TAILQ_REMOVE(&sdlims, sdlim, entries); syslog(LOG_INFO, "%s has disappeared", sdlim->dxname); destroy_sdlim(sdlim); sdlim = next; } continue; } if (sdlim != NULL && i == sdlim->dev) { if (strcmp(sdlim->dxname, sensordev.xname) == 0) { check_sdlim(sdlim, this_check); continue; } else { next = TAILQ_NEXT(sdlim, entries); TAILQ_REMOVE(&sdlims, sdlim, entries); syslog(LOG_INFO, "%s has been replaced", sdlim->dxname); destroy_sdlim(sdlim); sdlim = next; } } next = create_sdlim(&sensordev); /* inserting next before sdlim */ if (sdlim != NULL) TAILQ_INSERT_BEFORE(sdlim, next, entries); else TAILQ_INSERT_TAIL(&sdlims, next, entries); syslog(LOG_INFO, "%s has appeared", next->dxname); sdlim = next; parse_config_sdlim(sdlim, configfile); check_sdlim(sdlim, this_check); } if (TAILQ_EMPTY(&sdlims)) return; /* Ensure that our queue is consistent. */ for (sdlim = TAILQ_FIRST(&sdlims); (next = TAILQ_NEXT(sdlim, entries)) != NULL; sdlim = next) if (sdlim->dev > next->dev) syslog(LOG_ALERT, "inconsistent sdlims queue"); } void check_sdlim(struct sdlim_t *sdlim, time_t this_check) { struct sensor sensor; struct limits_t *limit; size_t len; int mib[5]; mib[0] = CTL_HW; mib[1] = HW_SENSORS; mib[2] = sdlim->dev; len = sizeof(sensor); TAILQ_FOREACH(limit, &sdlim->limits, entries) { if ((limit->flags & SENSORSD_L_ISTATUS) && !(limit->flags & SENSORSD_L_USERLIMIT)) continue; mib[3] = limit->type; mib[4] = limit->numt; if (sysctl(mib, 5, &sensor, &len, NULL, 0) == -1) err(1, "sysctl"); if (!(limit->flags & SENSORSD_L_ISTATUS)) { enum sensor_status newastatus = sensor.status; if (limit->astatus != newastatus) { if (limit->astatus2 != newastatus) { limit->astatus2 = newastatus; limit->acount = 0; } else if (++limit->acount >= 3) { limit->last_val = sensor.value; limit->astatus2 = limit->astatus = newastatus; limit->astatus_changed = this_check; } } } if (limit->flags & SENSORSD_L_USERLIMIT) { enum sensorsd_s_status newustatus; if (sensor.flags & SENSOR_FINVALID) newustatus = SENSORSD_S_INVALID; else if (sensor.value > limit->upper) newustatus = SENSORSD_S_ABOVE; else if (sensor.value < limit->lower) newustatus = SENSORSD_S_BELOW; else newustatus = SENSORSD_S_WITHIN; if (limit->ustatus != newustatus) { if (limit->ustatus2 != newustatus) { limit->ustatus2 = newustatus; limit->ucount = 0; } else if (++limit->ucount >= 3) { limit->last_val = sensor.value; limit->ustatus2 = limit->ustatus = newustatus; limit->ustatus_changed = this_check; } } } } } void execute(char *command) { char *argp[] = {"sh", "-c", command, NULL}; switch (fork()) { case -1: syslog(LOG_CRIT, "execute: fork() failed"); break; case 0: execv("/bin/sh", argp); _exit(1); /* NOTREACHED */ default: break; } } void report(time_t last_report) { struct sdlim_t *sdlim; TAILQ_FOREACH(sdlim, &sdlims, entries) report_sdlim(sdlim, last_report); } void report_sdlim(struct sdlim_t *sdlim, time_t last_report) { struct limits_t *limit; TAILQ_FOREACH(limit, &sdlim->limits, entries) { if ((limit->astatus_changed <= last_report) && (limit->ustatus_changed <= last_report)) continue; if (limit->astatus_changed > last_report) { const char *as = NULL; switch (limit->astatus) { case SENSOR_S_UNSPEC: as = ""; break; case SENSOR_S_OK: as = ", OK"; break; case SENSOR_S_WARN: as = ", WARN"; break; case SENSOR_S_CRIT: as = ", CRITICAL"; break; case SENSOR_S_UNKNOWN: as = ", UNKNOWN"; break; } syslog(limit->astatus == SENSOR_S_OK ? LOG_INFO : LOG_ALERT, "%s.%s%d: %s%s", sdlim->dxname, sensor_type_s[limit->type], limit->numt, print_sensor(limit->type, limit->last_val), as); } if (limit->ustatus_changed > last_report) { char us[BUFSIZ]; switch (limit->ustatus) { case SENSORSD_S_UNSPEC: snprintf(us, sizeof(us), "ustatus uninitialised"); break; case SENSORSD_S_INVALID: snprintf(us, sizeof(us), "marked invalid"); break; case SENSORSD_S_WITHIN: snprintf(us, sizeof(us), "within limits: %s", print_sensor(limit->type, limit->last_val)); break; case SENSORSD_S_ABOVE: snprintf(us, sizeof(us), "exceeds limits: %s is above %s", print_sensor(limit->type, limit->last_val), print_sensor(limit->type, limit->upper)); break; case SENSORSD_S_BELOW: snprintf(us, sizeof(us), "exceeds limits: %s is below %s", print_sensor(limit->type, limit->last_val), print_sensor(limit->type, limit->lower)); break; } syslog(limit->ustatus == SENSORSD_S_WITHIN ? LOG_INFO : LOG_ALERT, "%s.%s%d: %s", sdlim->dxname, sensor_type_s[limit->type], limit->numt, us); } if (limit->command) { int i = 0, n = 0, r; char *cmd = limit->command; char buf[BUFSIZ]; int len = sizeof(buf); buf[0] = '\0'; for (i = n = 0; n < len; ++i) { if (cmd[i] == '\0') { buf[n++] = '\0'; break; } if (cmd[i] != '%') { buf[n++] = limit->command[i]; continue; } i++; if (cmd[i] == '\0') { buf[n++] = '\0'; break; } switch (cmd[i]) { case 'x': r = snprintf(&buf[n], len - n, "%s", sdlim->dxname); break; case 't': r = snprintf(&buf[n], len - n, "%s", sensor_type_s[limit->type]); break; case 'n': r = snprintf(&buf[n], len - n, "%d", limit->numt); break; case 'l': { char *s = ""; switch (limit->ustatus) { case SENSORSD_S_UNSPEC: s = "uninitialised"; break; case SENSORSD_S_INVALID: s = "invalid"; break; case SENSORSD_S_WITHIN: s = "within"; break; case SENSORSD_S_ABOVE: s = "above"; break; case SENSORSD_S_BELOW: s = "below"; break; } r = snprintf(&buf[n], len - n, "%s", s); break; } case 's': { char *s; switch (limit->astatus) { case SENSOR_S_UNSPEC: s = "UNSPEC"; break; case SENSOR_S_OK: s = "OK"; break; case SENSOR_S_WARN: s = "WARNING"; break; case SENSOR_S_CRIT: s = "CRITICAL"; break; default: s = "UNKNOWN"; } r = snprintf(&buf[n], len - n, "%s", s); break; } case '2': r = snprintf(&buf[n], len - n, "%s", print_sensor(limit->type, limit->last_val)); break; case '3': r = snprintf(&buf[n], len - n, "%s", print_sensor(limit->type, limit->lower)); break; case '4': r = snprintf(&buf[n], len - n, "%s", print_sensor(limit->type, limit->upper)); break; default: r = snprintf(&buf[n], len - n, "%%%c", cmd[i]); break; } if (r == -1 || (r >= len - n)) { syslog(LOG_CRIT, "could not parse " "command"); return; } if (r > 0) n += r; } if (buf[0]) execute(buf); } } } const char *drvstat[] = { NULL, "empty", "ready", "powerup", "online", "idle", "active", "rebuild", "powerdown", "fail", "pfail" }; static char * print_sensor(enum sensor_type type, int64_t value) { static char rfbuf[RFBUFCNT][RFBUFSIZ]; /* ring buffer */ static int idx; char *fbuf; fbuf = rfbuf[idx++]; if (idx == RFBUFCNT) idx = 0; switch (type) { case SENSOR_TEMP: snprintf(fbuf, RFBUFSIZ, "%.2f degC", (value - 273150000) / 1000000.0); break; case SENSOR_FANRPM: snprintf(fbuf, RFBUFSIZ, "%lld RPM", value); break; case SENSOR_VOLTS_DC: snprintf(fbuf, RFBUFSIZ, "%.2f V DC", value / 1000000.0); break; case SENSOR_VOLTS_AC: snprintf(fbuf, RFBUFSIZ, "%.2f V AC", value / 1000000.0); break; case SENSOR_WATTS: snprintf(fbuf, RFBUFSIZ, "%.2f W", value / 1000000.0); break; case SENSOR_AMPS: snprintf(fbuf, RFBUFSIZ, "%.2f A", value / 1000000.0); break; case SENSOR_WATTHOUR: snprintf(fbuf, RFBUFSIZ, "%.2f Wh", value / 1000000.0); break; case SENSOR_AMPHOUR: snprintf(fbuf, RFBUFSIZ, "%.2f Ah", value / 1000000.0); break; case SENSOR_INDICATOR: snprintf(fbuf, RFBUFSIZ, "%s", value? "On" : "Off"); break; case SENSOR_INTEGER: snprintf(fbuf, RFBUFSIZ, "%lld", value); break; case SENSOR_PERCENT: snprintf(fbuf, RFBUFSIZ, "%.2f%%", value / 1000.0); break; case SENSOR_LUX: snprintf(fbuf, RFBUFSIZ, "%.2f lx", value / 1000000.0); break; case SENSOR_DRIVE: if (0 < value && value < sizeof(drvstat)/sizeof(drvstat[0])) snprintf(fbuf, RFBUFSIZ, "%s", drvstat[value]); else snprintf(fbuf, RFBUFSIZ, "%lld ???", value); break; case SENSOR_TIMEDELTA: snprintf(fbuf, RFBUFSIZ, "%.6f secs", value / 1000000000.0); break; case SENSOR_HUMIDITY: snprintf(fbuf, RFBUFSIZ, "%.2f%%", value / 1000.0); break; case SENSOR_FREQ: snprintf(fbuf, RFBUFSIZ, "%.2f Hz", value / 1000000.0); break; case SENSOR_ANGLE: snprintf(fbuf, RFBUFSIZ, "%lld", value); break; case SENSOR_DISTANCE: snprintf(fbuf, RFBUFSIZ, "%.3f m", value / 1000000.0); break; case SENSOR_PRESSURE: snprintf(fbuf, RFBUFSIZ, "%.2f Pa", value / 1000.0); break; case SENSOR_ACCEL: snprintf(fbuf, RFBUFSIZ, "%2.4f m/s^2", value / 1000000.0); break; case SENSOR_VELOCITY: snprintf(fbuf, RFBUFSIZ, "%4.3f m/s", value / 1000000.0); break; default: snprintf(fbuf, RFBUFSIZ, "%lld ???", value); } return (fbuf); } void parse_config(char *cf) { struct sdlim_t *sdlim; TAILQ_FOREACH(sdlim, &sdlims, entries) parse_config_sdlim(sdlim, cf); } void parse_config_sdlim(struct sdlim_t *sdlim, char *cf) { struct limits_t *p; char *buf = NULL, *ebuf = NULL; char node[48]; char *cfa[2]; cfa[0] = cf; cfa[1] = NULL; TAILQ_FOREACH(p, &sdlim->limits, entries) { snprintf(node, sizeof(node), "hw.sensors.%s.%s%d", sdlim->dxname, sensor_type_s[p->type], p->numt); p->flags = 0; if (cgetent(&buf, cfa, node) != 0) if (cgetent(&buf, cfa, sensor_type_s[p->type]) != 0) continue; if (cgetcap(buf, "istatus", ':')) p->flags |= SENSORSD_L_ISTATUS; if (cgetstr(buf, "low", &ebuf) < 0) ebuf = NULL; p->lower = get_val(ebuf, 0, p->type); if (cgetstr(buf, "high", &ebuf) < 0) ebuf = NULL; p->upper = get_val(ebuf, 1, p->type); if (cgetstr(buf, "command", &ebuf) < 0) ebuf = NULL; if (ebuf != NULL) { p->command = ebuf; ebuf = NULL; } free(buf); buf = NULL; if (p->lower != LLONG_MIN || p->upper != LLONG_MAX) p->flags |= SENSORSD_L_USERLIMIT; } } int64_t get_val(char *buf, int upper, enum sensor_type type) { double val; int64_t rval = 0; char *p; if (buf == NULL) { if (upper) return (LLONG_MAX); else return (LLONG_MIN); } val = strtod(buf, &p); if (buf == p) err(1, "incorrect value: %s", buf); switch (type) { case SENSOR_TEMP: switch (*p) { case 'C': printf("C"); rval = val * 1000 * 1000 + 273150000; break; case 'F': printf("F"); rval = (val * 1000 * 1000 + 459670000) / 9 * 5; break; default: errx(1, "unknown unit %s for temp sensor", p); } break; case SENSOR_FANRPM: rval = val; break; case SENSOR_VOLTS_DC: case SENSOR_VOLTS_AC: if (*p != 'V') errx(1, "unknown unit %s for voltage sensor", p); rval = val * 1000 * 1000; break; case SENSOR_PERCENT: rval = val * 1000.0; break; case SENSOR_INDICATOR: case SENSOR_INTEGER: case SENSOR_DRIVE: case SENSOR_ANGLE: rval = val; break; case SENSOR_WATTS: case SENSOR_AMPS: case SENSOR_WATTHOUR: case SENSOR_AMPHOUR: case SENSOR_LUX: case SENSOR_FREQ: case SENSOR_ACCEL: case SENSOR_DISTANCE: case SENSOR_VELOCITY: rval = val * 1000 * 1000; break; case SENSOR_TIMEDELTA: rval = val * 1000 * 1000 * 1000; break; case SENSOR_HUMIDITY: case SENSOR_PRESSURE: rval = val * 1000.0; break; default: errx(1, "unsupported sensor type"); /* not reached */ } free(buf); return (rval); } /* ARGSUSED */ void reparse_cfg(int signo) { reload = 1; }