xref: /openbsd-src/usr.sbin/sensorsd/sensorsd.c (revision 799f675f6700f14e59124f9825c723e9f2ce19dc)
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